Как заставить Hibernate возвращать даты как java.util.Date вместо Timestamp? - программирование
Подтвердить что ты не робот

Как заставить 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 в DB:

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

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

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

Примечание:

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

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

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

    @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/

4b9b3361

Ответ 1

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

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

  • Класс, содержащий базовое сопоставление типов 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 на мой пользовательский объект, который сможет узнать точный тип для преобразования.

Решение:

  • Создайте собственный менеджер сущностей контейнера factory, расширив класс 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, а затем создайте собственный диспетчер сущностей factory.

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

Ответ 2

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

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());
    }

}

Ответ 3

Подходы 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 java в базу данных и из нее.

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

  • Внесите UserType, чтобы выполнить фактическую сантехнику (преобразование в/из java.util.Date), названное, например. TimestampAsJavaUtilDateType
  • Добавить аннотацию @TypeDef на одном объекте или в package-info.java - оба будут доступны по всему миру для сеанса factory (см. руководство по ссылке выше). Вы можете использовать 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;
       [...]
    }
    

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

Ответ 4

Вот решение для 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.

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

Я столкнулся с проблемой с этим, так же как и мои JUnit assertEquals не сравнивали Dates to Hibernate с испущенными типами java.util.Date(которые, как описано в вопросе, действительно Timestamps). Оказывается, изменяя отображение на '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 for 'date' и Timestamp для 'java.util.Date').