Как заставить Hibernate возвращать даты как java.util.Date вместо Timestamp?

ситуация:

У меня есть постоянный класс с переменной типа java.util.Date:

import java.util.Date;

@Entity
@Table(name = "prd_period")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Period extends ManagedEntity implements Interval {

   @Column(name = "startdate_", nullable = false)
   private Date startDate;

}

Соответствующая таблица в БД:

CREATE TABLE 'prd_period' (
  'id_' bigint(20) NOT NULL AUTO_INCREMENT,
 ...
  'startdate_' datetime NOT NULL
)

Затем я сохраняю свой объект Period в БД:

Period p = new Period();
Date d = new Date();
p.setStartDate();
myDao.save(p);

После этого, если я пытаюсь извлечь свой объект из БД, он возвращается с переменной startDate типа Timestamp - и все места, где я пытаюсь использовать equals (...), возвращают false.

Вопрос: есть ли какие-либо средства, чтобы заставить Hibernate возвращать даты как объект типа java.util.Date вместо Timestamp без явного изменения каждой такой переменной (например, она должна иметь возможность просто работать, без явного изменения существующих переменных java.util. Тип даты)?

НОТА:

Я нашел ряд явных решений, где используются аннотации или модифицируется сеттер, но у меня есть много классов с переменными даты, поэтому мне нужно какое-то централизованное решение, и все, что описано ниже, недостаточно хорошо:

Использование аннотации @Type: - java.sql. Дата будет возвращена

@Column
@Type(type="date")
private Date startDate;

Использование аннотации @Temporal (TemporalType.DATE) - java.sql. Дата будет возвращена

@Temporal(TemporalType.DATE)
@Column(name=”CREATION_DATE”)
private Date startDate;

Изменяя сеттер (глубокая копия) - java.util.Date будет возвращен

public void setStartDate(Date startDate) {
    if (startDate != null) {
        this.startDate = new Date(startDate.getTime());
    } else {
        this.startDate = null;
    }
}

При создании моего собственного типа: - java.util.Date будет возвращен

Подробности приведены здесь:http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/

 Zoidberg02 мар. 2012 г., 14:23
Я думаю, что вы должны использовать один из явных ответов, которые вы только что показали. В основном для поддержки кода в будущем. Это позволит новому разработчику точно увидеть, что вы делаете, вместо того, чтобы тратить много времени на выяснение того, почему даты в вашей системе работают иначе, чем в любой другой спящей системе.
 tscho03 мар. 2012 г., 13:57
Если изменение классов Java является вариантом, возможно, лучшим решением было бы обойтись безjava.util.Date в целом и использоватьJoda времени вместо. Также есть специальныйПроект поддержки Hibernate или более обобщенныйПользовательский проект доступны для использования с Joda-Time.
 Denis Tulskiy02 мар. 2012 г., 15:59
Второе решение выглядит лучше для меня. Кроме того, из вашего блога кажется, что вы используете hibernate для создания своей структуры БД. Что делать, если вы берете это под свой контроль и просто используетеdate вместоtimestamp везде?
 tscho02 мар. 2012 г., 22:45
+1 Это действительно неприятная проблема. Я работаю с Hibernate уже много лет и не знаюЛюбые официальная документация Hibernate относительно этого. Я используюмодифицированный сеттер подход. Я также делаю глубокую копию в получателе, потому чтоjava.util.Date не является неизменным, поэтому в любом случае предпочтительнее использовать глубокую копию.
 JB Nizet03 мар. 2012 г., 00:33
Обратите внимание, что подход сеттера ничего не сделает, если вы используете доступ к полю, а не доступ к свойству.

Ответы на вопрос(7)

то создание нового java.util.Date в установщике для свойства date в вашем постоянном бине, например:

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Column;

@Entity
public class Purchase {

    private Date date;

    @Column
    public Date getDate() {
        return this.date;
    }

    public void setDate(Date date) {
        // force java.sql.Timestamp to be set as a java.util.Date
        this.date = new Date(date.getTime());
    }

}
 Wrench15 февр. 2017 г., 17:53
public void getDate() не может вернуть ссылку на тип Дата.

так как мои JUnit assertEquals не смогли сравнить Dates с испускаемыми Hibernate типами 'java.util.Date' (которые, как описано в вопросе, действительно являются метками времени). Оказывается, изменяя отображение на «date», а не «java.util.Date», Hibernate генерирует элементы java.util.Date. Я использую файл сопоставления XML с версией Hibernate 4.1.12.

Эта версия испускает 'java.util.Timestamp':

<property name="date" column="DAY" type="java.util.Date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />

Эта версия испускает 'java.util.Date':

<property name="date" column="DAY" type="date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />

Обратите внимание, однако, что если Hibernate используется для генерации DDL, они будут генерировать различные типы SQL (Date для «date» и Timestamp для «java.util.Date»).

Решение Вопроса

я провел некоторое время с этой проблемой и нашел решение. Это не красиво, но, по крайней мере, отправная точка - возможно, кто-то дополнит это некоторыми полезными комментариями.

Некоторая информация о картографии, которую я нашел в процессе:

Класс, который содержит базовое отображение типов Hibernate на типы свойств - org.hibernate.type.TypeFactory. Все эти отображения хранятся в неизменяемой карте

private static final Map BASIC_TYPES;
...
basics.put( java.util.Date.class.getName(), Hibernate.TIMESTAMP );
...
BASIC_TYPES = Collections.unmodifiableMap( basics );

Как вы можете видеть с типом java.util.Date, связанным с типом Hibernate org.hibernate.type.TimestampType

Следующий интересный момент - создание Hibernate org.hibernate.cfg.Configuration - объекта, который содержит всю информацию о сопоставленных классах. Эти классы и их свойства могут быть извлечены следующим образом:

Iterator clsMappings = cfg.getClassMappings();
while(clsMappings.hasNext()){
    PersistentClass mapping = (PersistentClass) clsMappings.next();
    handleProperties(mapping.getPropertyIterator(), map);
}

Подавляющее большинство свойств являются объектами типов org.hibernate.mapping.SimpleValue. Наш интерес представляет метод SimpleValue.getType () - в этом методе определяется, какой тип будет использоваться для преобразования значений свойств туда-обратно при работе с БД.

Type result = TypeFactory.heuristicType(typeName, typeParameters);

На данный момент я понимаю, что я не могу изменить BASIC_TYPES - единственный способ - заменить объект SimpleValue на свойства типов java.util.Date для моего пользовательского объекта, который сможет знать точный тип для преобразования.

Решение:

Создайте собственную фабрику диспетчера сущностей контейнера, расширив класс HibernatePersistence и переопределив его метод createContainerEntityManagerFactory:

public class HibernatePersistenceExtensions extends HibernatePersistence {

    @Override
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) {

        if ("true".equals(map.get("hibernate.use.custom.entity.manager.factory"))) {
            return CustomeEntityManagerFactoryFactory.createCustomEntityManagerFactory(info, map);
        } else {
            return super.createContainerEntityManagerFactory(info, map);
        }
    }
}

Создайте объект конфигурации Hibernate, измените значения объектов для свойств java.util.Date, а затем создайте собственную фабрику диспетчера сущностей.

public class ReattachingEntityManagerFactoryFactory {


    @SuppressWarnings("rawtypes")
    public static EntityManagerFactory createContainerEntityManagerFactory(
    PersistenceUnitInfo info, Map map) {
        Ejb3Configuration cfg = new Ejb3Configuration();

        Ejb3Configuration configured = cfg.configure( info, map );

        handleClassMappings(cfg, map);

        return configured != null ? configured.buildEntityManagerFactory() : null;
    }

    @SuppressWarnings("rawtypes")
    private static void handleClassMappings(Ejb3Configuration cfg, Map map) {
        Iterator clsMappings = cfg.getClassMappings();
        while(clsMappings.hasNext()){
             PersistentClass mapping = (PersistentClass) clsMappings.next();
             handleProperties(mapping.getPropertyIterator(), map);
        }
    } 



    private static void handleProperties(Iterator props, Map map) {

        while(props.hasNext()){
             Property prop = (Property) props.next();
             Value value = prop.getValue();
             if (value instanceof Component) {
                 Component c = (Component) value;
                 handleProperties(c.getPropertyIterator(), map);
             } else {

                 handleReturnUtilDateInsteadOfTimestamp(prop, map);

             }
         }

    private static void handleReturnUtilDateInsteadOfTimestamp(Property prop, Map map) {
        if ("true".equals(map.get("hibernate.return.date.instead.of.timestamp"))) {
            Value value = prop.getValue();

            if (value instanceof SimpleValue) {
                SimpleValue simpleValue = (SimpleValue) value;
                String typeName = simpleValue.getTypeName();
                if ("java.util.Date".equals(typeName)) {
                    UtilDateSimpleValue udsv = new UtilDateSimpleValue(simpleValue);
                    prop.setValue(udsv);
                }
            }
        }
    }

}

Как видите, я просто перебираю каждое свойство и заменяю SimpleValue-объект на UtilDateSimpleValue для свойств типа java.util.Date. Это очень простой класс - он реализует тот же интерфейс, что и объект SimpleValue, например, org.hibernate.mapping.KeyValue. В конструктор передается оригинальный объект SimpleValue - поэтому каждый вызов UtilDateSimpleValue перенаправляется на исходный объект с одним исключением - метод getType (...) возвращает мой пользовательский тип.

public class UtilDateSimpleValue implements KeyValue{

    private SimpleValue value;

    public UtilDateSimpleValue(SimpleValue value) {
        this.value = value;
    }

    public SimpleValue getValue() {
        return value;
    }

    @Override
    public int getColumnSpan() {
        return value.getColumnSpan();
    }

    ...

    @Override
    public Type getType() throws MappingException {
        final String typeName = value.getTypeName();

        if (typeName == null) {
                throw new MappingException("No type name");
        }

        Type result = new UtilDateUserType();

        return result;
    }
    ...
}

И последний шаг - реализация UtilDateUserType. Я просто расширяю оригинальный org.hibernate.type.TimestampType и переопределяю его метод get () следующим образом:

public class UtilDateUserType extends TimestampType{

    @Override
    public Object get(ResultSet rs, String name) throws SQLException {
        Timestamp ts = rs.getTimestamp(name);

        Date result = null;
        if(ts != null){
            result = new Date(ts.getTime());
        }

        return result;
    }
}

Вот и все. Немного хитро, но теперь каждое свойство java.util.Date возвращается как java.util.Date без каких-либо дополнительных модификаций существующего кода (аннотации или изменение сеттеров). Как я выяснил в Hibernate 4 или выше, есть гораздо более простой способ заменить ваш собственный тип (подробности см. Здесь:Hibernate TypeResolver). Любые предложения или критика приветствуются.

которые расширяют инстанцируемый класс и добавляют компонент значения. Например, java.sql.Timestamp расширяет java.util.Date и добавляет поле наносекунд. Реализация equals для Timestamp нарушает симметрию и может привести к ошибочному поведению, если объекты Timestamp и Date используются в одной коллекции или смешаны иным образом. В классе Timestamp есть оговорка, предупреждающая программистов о смешивании дат и временных отметок. Несмотря на то, что вы не будете сталкиваться с проблемами, пока будете хранить их отдельно, ничто не помешает вам смешивать их, и возникающие ошибки могут быть трудно отладить. Такое поведение класса Timestamp было ошибкой и не должно имитироваться.

проверить эту ссылку

http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/

java.sql.Date объекты, согласно спецификации JPA / Hibernate, а неjava.util.Date, Из подходов 3 и 4 я бы предпочел выбрать последний, потому что он более декларативный и будет работать как с полевыми, так и с геттерными аннотациями.

Вы уже изложили решение 4 в своем блоге, на который ссылаетесь, как любезно указал @tscho. Может бытьdefaultForType (см. ниже) должен дать вамцентрализованное решение ты искал. Конечно, все равно нужно будет различать поля даты (без времени) и метки времени.

Для дальнейшего использования я оставлю сводку использования вашего собственного HibernateUserType Вот:

Чтобы Hibernate дал вамjava.util.Date экземпляры, вы можете использовать@Тип а также@TypeDef аннотации, чтобы определить другое отображение ваших java-типов java.util.Date в и из базы данных.

Смотрите примеры в основном справочном руководстве.Вот.

РеализоватьUserType выполнить фактическую сантехнику (преобразование в / из java.util.Date), например, с именемTimestampAsJavaUtilDateType

Добавьте аннотацию @TypeDef для одной сущности или в package-info.java - обе будут доступны глобально для фабрики сеансов (см. Ссылку на руководство выше). Вы можете использоватьdefaultForType применить преобразование типа ко всем сопоставленным полям типаjava.util.Date.

@TypeDef
  name = "timestampAsJavaUtilDate",
  defaultForType = java.util.Date.class, /* applied globally */
  typeClass = TimestampAsJavaUtilDateType.class
)

Опционально вместоdefaultForType, вы можете аннотировать свои поля / получатели с помощью @Type индивидуально:

@Entity
public class MyEntity {
   [...]
   @Type(type="timestampAsJavaUtilDate")
   private java.util.Date myDate;
   [...]
}

Постскриптум Чтобы предложить совершенно другой подход: мы обычно просто не сравниваем объекты Date, используя equals (). Вместо этого мы используем служебный класс с методами для сравнения, например. только календарная дата двух экземпляров Date (или другое разрешение, например секунды), независимо от точного типа реализации. Это хорошо сработало для нас.

 Michael Paesold03 мар. 2012 г., 18:40
@tscho Вы правы, я полностью пропустил сообщение в блоге. Я отредактирую свой ответ, чтобы отразить это, хотя я все еще думаю, что он полезен, потому что он здесь на stackoverflow вместо внешнего блога.
 tscho03 мар. 2012 г., 13:45
ОП предлагает этот подход в пункте 4 возможных решений.связанный пост в блоге также упоминает тонкий побочный эффект этого подхода: преобразование изjava.sql.Timestamp вjava.util.Date падает наносекундная точность. Это также верно длямодифицированный сеттер подход. Вот почему я также определяю столбцы меток времени в базе данных только с точностью до миллисекунды.

@Temporal(TemporalType.DATE) дляjava.util.Date поле в вашем классе сущности.

Более подробная информация доступна вэто stackoverflow ответ.

pacakge-info.java содержит

@TypeDefs(
    {
        @TypeDef(
                name = "javaUtilDateType",
                defaultForType = java.util.Date.class,
                typeClass = JavaUtilDateType.class
        )
    })
package some.pack;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;

И JavaUtilDateType:

package some.other.or.same.pack;

import java.sql.Timestamp;
import java.util.Comparator;
import java.util.Date;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.LiteralType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.VersionType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor;
import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor;

/**
 * Note: Depends on hibernate implementation details hibernate-core-4.3.7.Final.
 *
 * @see
 * <a href="http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch06.html#types-custom">Hibernate
 * Documentation</a>
 * @see TimestampType
 */
public class JavaUtilDateType
        extends AbstractSingleColumnStandardBasicType<Date>
        implements VersionType<Date>, LiteralType<Date> {

    public static final TimestampType INSTANCE = new TimestampType();

    public JavaUtilDateType() {
        super(
                TimestampTypeDescriptor.INSTANCE,
                new JdbcTimestampTypeDescriptor() {

                    @Override
                    public Date fromString(String string) {
                        return new Date(super.fromString(string).getTime());
                    }

                    @Override
                    public <X> Date wrap(X value, WrapperOptions options) {
                        return new Date(super.wrap(value, options).getTime());
                    }

                }
        );
    }

    @Override
    public String getName() {
        return "timestamp";
    }

    @Override
    public String[] getRegistrationKeys() {
        return new String[]{getName(), Timestamp.class.getName(), java.util.Date.class.getName()};
    }

    @Override
    public Date next(Date current, SessionImplementor session) {
        return seed(session);
    }

    @Override
    public Date seed(SessionImplementor session) {
        return new Timestamp(System.currentTimeMillis());
    }

    @Override
    public Comparator<Date> getComparator() {
        return getJavaTypeDescriptor().getComparator();
    }

    @Override
    public String objectToSQLString(Date value, Dialect dialect) throws Exception {
        final Timestamp ts = Timestamp.class.isInstance(value)
                ? (Timestamp) value
                : new Timestamp(value.getTime());
        // TODO : use JDBC date literal escape syntax? -> {d 'date-string'} in yyyy-mm-dd hh:mm:ss[.f...] format
        return StringType.INSTANCE.objectToSQLString(ts.toString(), dialect);
    }

    @Override
    public Date fromStringValue(String xml) throws HibernateException {
        return fromString(xml);
    }
}

Это решение в основном опирается на реализацию TimestampType с добавлением дополнительного поведения через анонимный класс типа JdbcTimestampTypeDescriptor.

 Vasile Bors06 мар. 2017 г., 17:34
Работай хорошо! Но в методе обертывания нужна проверка нуля.

Ваш ответ на вопрос