Как заставить Hibernate возвращать даты как java.утиль.Дата вместо отметки времени?


ситуация:

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

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 p = new Period();
Date d = new Date();
p.setStartDate();
myDao.save(p);

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

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

Примечание:

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

  1. использование аннотации @Type: - java.язык SQL.Дата будет возвращена

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

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

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

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

7 51

7 ответов:

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

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

  • класс, который содержит базовое сопоставление типов гибернации с типами свойств, является org.зимовать.тип.Типография. Все эти сопоставления хранятся в неизменяемой карте

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

как вы можете видеть с Java.утиль.Тип даты ассоциируется с типом гибернации org.зимовать.тип.TimestampType

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

    Iterator clsMappings = cfg.getClassMappings();
    while(clsMappings.hasNext()){
        PersistentClass mapping = (PersistentClass) clsMappings.next();
        handleProperties(mapping.getPropertyIterator(), map);
    }
    
  • подавляющее большинство свойств объектов орг.зимовать.отображение.Типы SimpleValue. Наш интерес представляет метод SimpleValue.getType ()-в этом методе определяется, какой тип будет использоваться для преобразования значений свойств взад-вперед при работе с DB

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

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

решение:

  • создать пользовательский контейнер entity manager 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, изменить значение ojects для java.утиль.Свойства даты, а затем создать собственную фабрику Entity manager.

    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.утиль.Дата. Это очень простой класс-он реализует тот же интерфейс, что и объект SimpleValue, например org.зимовать.отображение.Ключевое значение. В конструкторе передается исходный объект 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. Я просто расширяю оригинальную организацию.зимовать.тип.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.утиль.Свойство Date возвращается как java.утиль.Дата без каких-либо дополнительных изменений существующего кода (аннотации или изменения сеттеров). Как я узнал в Hibernate 4 или выше, есть гораздо более простой способ заменить свой собственный тип (см. подробности здесь: Hibernate TypeResolver). Любые предложения или критика приветствуются.

простой альтернативой использованию пользовательского типа пользователя является создание новой java.утиль.Дата в сеттере для свойства 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());
    }

}

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

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

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

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

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

  1. реализовать a UserType чтобы сделать фактическую сантехнику (преобразование в/из java.утиль.Дата), названный например TimestampAsJavaUtilDateType
  2. добавьте аннотацию @TypeDef на один объект или в пакет-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. чтобы предложить совершенно другой подход: мы обычно просто не сравнить даты объектов с помощью метода Equals() в любом случае. Вместо этого мы используем служебный класс с методами Для сравнения, например, только календарной даты двух экземпляров даты (или другого разрешения, такого как секунды), независимо от точный тип реализации. Это так же хорошо работало для нас.

вот решение для гибернации 4.3.7.Окончательный.

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.

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

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

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

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

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

я столкнулся с проблемой с этим, а также мои JUnit assertEquals не смогли сравнить даты с Hibernate emitted ' java.утиль.Типы даты (которые, как описано в вопросе, действительно являются метками времени). Оказывается, что путем изменения отображения на "дату", а не " java.утиль.Дата ' Hibernate генерирует java.утиль.Дата участников. Я использую XML-файл сопоставления с Hibernate версии 4.1.12.

эта версия выдает ' java.утиль.Метка времени':

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

эта версия излучает - Ява.утиль.Дата:

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

Примечание, однако, если Hibernate используется для создания DDL, то они будут генерировать различные типы SQL (дата для "дата" и метка времени для " java.утиль.Дата').