Как заставить Hibernate возвращать даты как java.util.Date вместо Timestamp? - PullRequest
53 голосов
/ 02 марта 2012

Ситуация :

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

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тип вместо метки времени без явного изменения каждой такой переменной (например, она должна иметь возможность просто работать, без явного изменения существующих переменных типа java.util.Date)?

ПРИМЕЧАНИЕ:

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

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

    @Column
    @Type(type="date")
    private Date startDate;
    
  2. Использование аннотации @Temporal (TemporalType.DATE) - java.sql.Date будет возвращено

    @Temporal(TemporalType.DATE)
    @Column(name=”CREATION_DATE”)
    private Date startDate;
    
  3. Путем изменения setter (глубокая копия) - java.util.Date будет возвращено

    public void setStartDate(Date startDate) {
        if (startDate != null) {
            this.startDate = new Date(startDate.getTime());
        } else {
            this.startDate = null;
        }
    }
    
  4. При создании моего собственного типа: - java.util.Date будет возвращено

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

Ответы [ 7 ]

9 голосов
/ 03 декабря 2012

Простая альтернатива использованию пользовательского типа UserType - это создание нового 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());
    }

}
9 голосов
/ 06 марта 2012

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

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

  • Класс, который содержит базовое отображение типов 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-object на 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 ). Любые предложения или критика приветствуются.

5 голосов
/ 03 марта 2012

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

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

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

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

См. Примеры в справочном руководстве по ядру здесь .

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

    @TypeDef
      name = "timestampAsJavaUtilDate",
      defaultForType = java.util.Date.class, /* applied globally */
      typeClass = TimestampAsJavaUtilDateType.class
    )
    
  3. При желании вместо defaultForType вы можете аннотировать свои поля / получатели с помощью @Type индивидуально:

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

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

3 голосов
/ 18 февраля 2015

Вот решение для Hibernate 4.3.7.Final.

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.

2 голосов
/ 15 февраля 2017

Просто добавьте эту аннотацию @Temporal(TemporalType.DATE) для поля java.util.Date в вашем классе сущности.

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

2 голосов
/ 04 марта 2012

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

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

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

0 голосов
/ 30 июля 2014

Я столкнулся с проблемой, так как мой 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»).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...