web-dev-qa-db-fra.com

Comment forcer Hibernate à renvoyer des dates au format Java.util.Date au lieu de Timestamp?

Situation :

J'ai une classe persistante avec une variable de type 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;

}

Table correspondante dans la base de données:

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

Ensuite, je sauvegarde mon objet Period dans la base de données:

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

Ensuite, si j'essaie d'extraire mon objet de la base de données, il est renvoyé avec la variable startDate de type Timestamp - et tous les emplacements où j'essaie d'utiliser equals (...) renvoient false.

Question : existe-t-il un moyen de forcer Hibernate à renvoyer des dates sous forme d'objet de type Java.util.Date au lieu de Horodatage sans modification explicite de chacune de ces variables ( Par exemple, il doit pouvoir fonctionner sans modification explicite de variables existantes de type Java.util.Date)?

NOTE:

J'ai trouvé un certain nombre de solutions explicites, où les annotations sont utilisées ou le setter est modifié - mais j'ai beaucoup de classes avec Date-variables - j'ai donc besoin d'une solution centralisée et tout ce qui est décrit ci-dessous n'est pas assez bon:

  1. Utilisation de l'annotation @Type: - Java.sql.Date sera renvoyé

    @Column
    @Type(type="date")
    private Date startDate;
    
  2. Utilisation de l'annotation @Temporal (TemporalType.DATE) - Java.sql.Date sera renvoyé

    @Temporal(TemporalType.DATE)
    @Column(name=”CREATION_DATE”)
    private Date startDate;
    
  3. En modifiant le séparateur (copie complète) - Java.util.Date sera renvoyé

    public void setStartDate(Date startDate) {
        if (startDate != null) {
            this.startDate = new Date(startDate.getTime());
        } else {
            this.startDate = null;
        }
    }
    
  4. Par la création de mon propre type: - Java.util.Date sera renvoyé

Les détails sont donnés ici: http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/

53
dim1902

J'ai donc passé un peu de temps avec ce problème et trouvé une solution. Ce n'est pas joli, mais au moins un point de départ - peut-être que quelqu'un complétera ceci avec quelques commentaires utiles.

Quelques informations sur le mappage que j'ai trouvées dans le processus:

  • La classe qui contient le mappage de base des types Hibernate aux types de propriété est org.hibernate.type.TypeFactory. Toutes ces mappages sont stockés dans une carte non modifiable

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

Comme vous pouvez le constater avec le type Java.util.Date associé au type Hibernate, org.hibernate.type.TimestampType

  • Prochain moment intéressant - création de Hibernate org.hibernate.cfg.Configuration - objet contenant toutes les informations sur les classes mappées. Ces classes et leurs propriétés peuvent être extraites comme ceci:

    Iterator clsMappings = cfg.getClassMappings();
    while(clsMappings.hasNext()){
        PersistentClass mapping = (PersistentClass) clsMappings.next();
        handleProperties(mapping.getPropertyIterator(), map);
    }
    
  • La grande majorité des propriétés sont les objets des types org.hibernate.mapping.SimpleValue. Notre point d’intérêt est la méthode SimpleValue.getType () - dans cette méthode est défini le type qui sera utilisé pour convertir les valeurs des propriétés dans les deux sens tout en travaillant avec DB.

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

À ce stade, je comprends que je ne parviens pas à modifier BASIC_TYPES - donc le seul moyen - de remplacer l’objet SimpleValue par les propriétés des types Java.util.Date par mon objet personnalisé, qui sera en mesure de connaître le type exact à convertir.

La solution:

  • Créez une fabrique de gestionnaires d'entités de conteneur personnalisés en étendant la classe HibernatePersistence et en substituant sa méthode 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);
            }
        }
    }
    
  • Créez un objet de configuration Hibernate, modifiez les propriétés des objets Java.util.Date, puis créez une fabrique de gestionnaires d'entités personnalisés.

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

Comme vous pouvez le constater, je parcoure chaque propriété et substitue UtilDateSimpleValue par objet SimpleValue par des propriétés de type Java.util.Date. C'est une classe très simple - elle implémente la même interface que l'objet SimpleValue, par exemple org.hibernate.mapping.KeyValue. Dans le constructeur, l'objet SimpleValue d'origine est transmis. Ainsi, chaque appel à UtilDateSimpleValue est redirigé vers l'objet d'origine avec une exception - méthode getType (...) renvoie mon Type personnalisé.

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;
    }
    ...
}
  • Et la dernière étape est la mise en œuvre de UtilDateUserType. Je viens d'étendre l'org.hibernate.type.TimestampType d'origine et de surcharger sa méthode get () comme ceci:

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

C'est tout. Un peu délicat, mais maintenant chaque propriété Java.util.Date est renvoyée en tant que Java.util.Date sans aucune modification supplémentaire du code existant (annotations ou modification des paramètres). Comme je le découvre dans Hibernate 4 ou version ultérieure, il existe un moyen beaucoup plus simple de remplacer votre propre type (voir les détails ici: Hibernate TypeResolver ). Toutes les suggestions ou critiques sont les bienvenues.

9
dim1902

Une alternative simple à l'utilisation d'un UserType personnalisé consiste à construire un nouveau fichier Java.util.Date dans le configurateur pour la propriété de date de votre bean persistant, par exemple:

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
Nathan

Les approches 1 et 2 ne fonctionnent évidemment pas, car vous obtenez Java.sql.Date objets, conformément aux spécifications JPA/Hibernate, et non Java.util.Date. Pour les approches 3 et 4, je préférerais choisir la dernière solution, car elle est plus déclarative et fonctionnera avec les annotations de champ et de lecture.

Vous avez déjà présenté la solution 4 dans votre article de blog référencé, comme @tscho a eu la gentillesse de le souligner. Peut-être que defaultForType (voir ci-dessous) devrait vous donner la solution centralisée que vous recherchiez. Bien sûr, il faudra quand même différencier les champs de date (sans heure) et d’horodatage.

Pour référence future, je laisserai le résumé de l’utilisation de votre propre Hibernate serType ici:

Pour que Hibernate te donne Java.util.Date instances, vous pouvez utiliser les annotations @ Type et @ TypeDef pour définir un mappage différent de votre Java.util.Date Java types à et de la base de données.

Voir les exemples dans le manuel de référence principal ici .

  1. Implémentez un serType pour effectuer la plomberie réelle (conversion vers/depuis Java.util.Date), nommée par exemple. TimestampAsJavaUtilDateType
  2. Ajoutez une annotation @TypeDef sur une entité ou dans un package-info.Java - les deux seront disponibles globalement pour la fabrique de sessions (voir le lien du manuel ci-dessus). Vous pouvez utiliser defaultForType pour appliquer la conversion de type à tous les champs mappés de type Java.util.Date.

    @TypeDef
      name = "timestampAsJavaUtilDate",
      defaultForType = Java.util.Date.class, /* applied globally */
      typeClass = TimestampAsJavaUtilDateType.class
    )
    
  3. Au lieu de defaultForType, vous pouvez éventuellement annoter vos champs/getters individuellement avec @Type:

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

P.S. Pour suggérer une approche totalement différente: nous ne comparons généralement pas les objets Date avec equals (). Au lieu de cela, nous utilisons une classe d’utilité avec des méthodes pour comparer, par exemple, seule la date du calendrier de deux instances Date (ou une autre résolution telle que secondes), quel que soit le type d'implémentation exact. Cela a bien fonctionné pour nous.

5
Michael Paesold

Voici la solution pour Hibernate 4.3.7.Final.

pacakge-info.Java contient

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

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

Cette solution repose principalement sur l’implémentation TimestampType et l’ajout de comportements supplémentaires via une classe anonyme de type JdbcTimestampTypeDescriptor.

3
Ilya Bystrov

Certaines bibliothèques de la Java) étendent une classe instantiable et ajoutent un composant de valeur. Par exemple, Java.sql.Timestamp étend Java.util.Date et ajoute un champ nanosecondes. L'implémentation d'equals pour Timestamp enfreint la symétrie et peut provoquer un comportement erratique si les objets Timestamp et Date sont utilisés dans la même collection ou s'ils sont mélangés d'une autre manière. tant que vous les gardez séparés, rien ne vous empêche de les mélanger et les erreurs qui en résultent peuvent être difficiles à résoudre, ce comportement de la classe Timestamp est une erreur et ne doit pas être imité.

consultez ce lien

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

2
flexJavaMysql

Il suffit d'ajouter la cette annotation @Temporal(TemporalType.DATE) pour un Java.util.Date champ dans votre classe d'entité.

Plus d'informations disponibles dans this stackoverflow answer.

2
ZiOS

J'ai rencontré un problème avec cela et mon asserEquals JUnit échouait en comparant les dates aux types 'Java.util.Date' émis par Hibernate (qui, comme décrit dans la question, sont vraiment des horodatages). En changeant le mappage en "date" plutôt qu'en "Java.util.Date", Hibernate génère des membres Java.util.Date. J'utilise un fichier de mappage XML avec Hibernate version 4.1.12.

Cette version émet 'Java.util.Timestamp':

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

Cette version émet 'Java.util.Date':

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

Notez cependant que si Hibernate est utilisé pour générer le DDL, ceux-ci généreront différents types SQL (Date pour 'date' et Horodatage pour 'Java.util.Date').

0
wbdarby