56 votes

Comment forcer Hibernate à retourner les dates en tant que java.util.Date au lieu de Timestamp ?

Situation :

J'ai une classe persistante avec des variables 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 BD :

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

Ensuite, je sauvegarde mon objet Période 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 endroits où j'essaie d'utiliser equals(...) renvoient false.

Pregunta : existe-t-il un moyen de forcer Hibernate à retourner les dates sous forme d'objets de type java.util.Date au lieu de Timestamp sans modification explicite de chacune de ces variables (par exemple, cela doit pouvoir fonctionner sans modification explicite des 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 des variables de date - donc j'ai 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 retournée.

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

    @Temporal(TemporalType.DATE)
    @Column(name=”CREATION_DATE”)
    private Date startDate;
  3. En modifiant le setter (copie profonde) - java.util.Date sera retourné

    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 retourné

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

6 votes

Je pense que vous devriez utiliser une des réponses explicites que vous venez de montrer. Principalement pour la maintenance du code à l'avenir. Cela permettra à un nouveau développeur de voir exactement ce que vous faites, au lieu de passer beaucoup de temps à essayer de comprendre pourquoi les dates de votre système fonctionnent différemment de tous les autres systèmes hibernate existants.

0 votes

La deuxième solution me semble la meilleure. En outre, d'après votre article de blog, il semble que vous vous appuyez sur Hibernate pour créer la structure de votre base de données. Et si vous preniez les choses en main et utilisiez simplement date au lieu de timestamp partout ?

1 votes

+1 C'est un problème désagréable. Je travaille avec Hibernate depuis des années maintenant et je ne connais pas de cualquier documentation officielle d'Hibernate à ce sujet. J'utilise le setter modifié approche. Je fais aussi une copie profonde dans le getter car java.util.Date n'est pas immuable, donc une copie profonde devrait être préférée de toute façon.

10voto

dim1902 Points 382

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

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

  • La classe qui contient le mappage de base des types Hibernate aux types de propriétés est org.hibernate.type.TypeFactory. Tous ces mappings 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 voir, le type java.util.Date est associé au type org.hibernate.type.TimestampType d'Hibernate.

  • Le moment intéressant suivant - la création de l'objet org.hibernate.cfg.Configuration - qui contient 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 des objets de type 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 pendant le travail avec la base de données.

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

À ce stade, je comprends que je ne suis pas en mesure de modifier BASIC_TYPES - donc la seule façon - de remplacer l'objet SimpleValue aux propriétés des types java.util.Date à mon objet personnalisé qui sera en mesure de savoir le type exact à convertir.

La solution :

  • Créez une usine personnalisée de gestionnaire d'entités de conteneur en étendant la classe HibernatePersistence et en surchargeant 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 l'objet de configuration Hibernate, modifiez les objets de valeur pour les propriétés java.util.Date et créez ensuite une usine de gestion d'entités personnalisée.

    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 voir, j'ai simplement itéré sur chaque propriété et remplacé SimpleValue-object par UtilDateSimpleValue pour les propriétés de type java.util.Date. Cette classe est 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 original est passé - ainsi chaque appel à UtilDateSimpleValue est redirigé vers l'objet original avec une exception - la méthode getType(...) retourne 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 l'implémentation de UtilDateUserType. J'étend simplement le type original org.hibernate.type.TimestampType et surcharge 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. C'est un peu délicat, mais maintenant chaque propriété de java.util.Date est retournée comme java.util.Date sans aucune modification supplémentaire du code existant (annotations ou modification des setters). Comme je l'ai découvert dans Hibernate 4 ou plus, il existe un moyen beaucoup plus facile de substituer votre propre type (voir les détails ici : Résolveur de types Hibernate ). Toute suggestion ou critique est la bienvenue.

10voto

Nathan Points 1836

Une alternative simple à l'utilisation d'un UserType personnalisé est de construire un nouveau java.util.Date dans le setter de la propriété 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());
    }

}

0 votes

public void getDate() ne peut pas retourner une référence au type Date.

5voto

Michael Paesold Points 307

Les approches 1 et 2 ne fonctionnent évidemment pas, parce que vous obtenez java.sql.Date selon la spécification de JPA/Hibernate, et non pas des java.util.Date . Parmi les approches 3 et 4, je préférerais choisir la dernière, parce qu'elle est plus déclarative et qu'elle fonctionnera avec les annotations de type field et getter.

Vous avez déjà exposé la solution 4 dans votre article de blog référencé, comme @tscho a bien voulu le souligner. Peut-être que defaultForType (voir ci-dessous) devrait vous donner le solution centralisée que vous recherchiez. Bien entendu, il faudra toujours faire la différence entre les champs de type date (sans heure) et horodatage.

Pour les références futures, je laisserai le résumé de l'utilisation de votre propre Hibernate. Type d'utilisateur ici :

Pour qu'Hibernate vous donne java.util.Date vous pouvez utiliser l'option @Type y @TypeDef pour définir un mappage différent de vos types java.util.Date vers et depuis la base de données.

Voir les exemples dans le manuel de référence du noyau aquí .

  1. Mettre en œuvre un Type d'utilisateur pour faire la plomberie réelle (conversion de/vers 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 session (voir le lien manuel ci-dessus). Vous pouvez utiliser defaultForType pour appliquer la conversion de type sur tous les champs mappés de type java.util.Date .

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

    @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 en utilisant equals() de toute façon. Nous utilisons plutôt une classe utilitaire avec des méthodes permettant de comparer, par exemple, uniquement la date du calendrier de deux instances de Date (ou une autre résolution telle que les secondes), quel que soit le type d'implémentation exact. Cela a bien fonctionné pour nous.

1 votes

Le PO suggère cette approche au point 4 des solutions possibles. Le site article de blog lié mentionne également un effet secondaire subtil de cette approche : la conversion de l java.sql.Timestamp a java.util.Date chute la précision de la nanoseconde. Ceci est également vrai pour le setter modifié approche. C'est pourquoi je définis également les colonnes d'horodatage dans la base de données uniquement avec une précision de l'ordre de la milliseconde.

1 votes

@tscho Vous avez raison, j'ai totalement manqué l'article du blog. Je vais modifier ma réponse pour refléter cela, bien que je pense toujours que c'est utile parce que c'est ici sur stackoverflow au lieu d'un blog externe.

0 votes

@MichaelPaesold Qu'est-ce que TimestampAsJavaUtilDateType.class ici ???

2voto

flexJavaMysql Points 54

Il existe quelques classes dans les bibliothèques de la plate-forme Java qui étendent une classe instanciable et ajoutent une composante de valeur. et ajoutent un composant de valeur. Par exemple, java.sql.Timestamp étend java.util.Date et ajoute un champ nanosecondes. L'implémentation de equals pour Timestamp viole la symétrie et peut entraîner un comportement erratique si les objets les objets Timestamp et Date sont utilisés dans la même collection ou sont mélangés d'une autre manière. La classe Timestamp comporte un avertissement mettant en garde les programmeurs contre le fait de de ne pas mélanger les dates et les horodatages. Vous n'aurez pas d'ennuis si vous les gardez séparés. les séparez, rien ne vous empêche de les mélanger, et les erreurs qui en résultent erreurs qui en résultent peuvent être difficiles à déboguer. Ce comportement de la classe Timestamp était une erreur erreur et ne doit pas être imité.

consultez ce lien

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

0voto

wbdarby Points 121

J'ai rencontré un problème avec cela, car mes assertEquals JUnit échouaient en comparant les dates aux types 'java.util.Date' émis par Hibernate (qui, comme décrit dans la question, sont en fait des timestamps). Il s'avère qu'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 toutefois que si Hibernate est utilisé pour générer les DDL, celles-ci généreront des types SQL différents (Date pour 'date' et Timestamp pour 'java.util.Date').

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X