webentwicklung-frage-antwort-db.com.de

Wie erzwinge ich Hibernate, Daten als Java.util.Date anstelle von Timestamp zurückzugeben?

Situation :

Ich habe eine persistable Klasse mit der Variablen Java.util.Date Typ:

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;

}

Entsprechende Tabelle im DB:

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

Dann speichere ich mein Periodenobjekt in DB:

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

Wenn ich dann versuche, mein Objekt aus der Datenbank zu extrahieren, wird es mit der Variablen startDate vom Typ Timestamp zurückgegeben - und alle Stellen, an denen ich Gleiche (...) verwenden möchte, geben false zurück.

Frage : Gibt es eine Möglichkeit, Hibernate zu zwingen, Daten als Objekt des Typs Java.util.Date anstelle von Timestamp zurückzugeben, ohne dass eine solche Variable explizit geändert wird? ( zB muss es einfach funktionieren, ohne explizite Änderung von existierenden Variablen vom Typ Java.util.Date)?

HINWEIS:

Ich habe eine Reihe von expliziten Lösungen gefunden, bei denen Annotationen verwendet oder Setter geändert wurden - aber ich habe viele Klassen mit Datumsvariablen -, also brauche ich eine zentralisierte Lösung und alles, was unten beschrieben wird, ist nicht gut genug:

  1. Verwendung der Annotation @Type: - Java.sql.Date wird zurückgegeben

    @Column
    @Type(type="date")
    private Date startDate;
    
  2. Verwendung der Annotation @Temporal (TemporalType.DATE) - Java.sql.Date wird zurückgegeben

    @Temporal(TemporalType.DATE)
    @Column(name=”CREATION_DATE”)
    private Date startDate;
    
  3. Durch Ändern des Setters (Deep Copy) wird Java.util.Date zurückgegeben

    public void setStartDate(Date startDate) {
        if (startDate != null) {
            this.startDate = new Date(startDate.getTime());
        } else {
            this.startDate = null;
        }
    }
    
  4. Bei Erstellung meines eigenen Typs: - Java.util.Date wird zurückgegeben

Details finden Sie hier: http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/

53
dim1902

Also habe ich einige Zeit mit diesem Problem verbracht und eine Lösung gefunden. Es ist nicht hübsch, aber zumindest ein Ausgangspunkt - vielleicht wird jemand dies mit einigen nützlichen Kommentaren ergänzen.

Einige Informationen zur Zuordnung, die ich im Prozess gefunden habe:

  • Die Klasse, die die grundlegende Zuordnung von Ruhezustandstypen zu Eigenschaftstypen enthält, ist org.hibernate.type.TypeFactory. Alle diese Zuordnungen werden in einer unveränderlichen Karte gespeichert

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

Wie Sie in Java.util.Date sehen können, ist der Typ Hibernate dem Typ org.hibernate.type.TimestampType zugeordnet

  • Nächster interessanter Moment - Erstellung von Hibernate org.hibernate.cfg.Configuration - Objekt, das alle Informationen über zugeordnete Klassen enthält. Diese Klassen und ihre Eigenschaften können folgendermaßen extrahiert werden:

    Iterator clsMappings = cfg.getClassMappings();
    while(clsMappings.hasNext()){
        PersistentClass mapping = (PersistentClass) clsMappings.next();
        handleProperties(mapping.getPropertyIterator(), map);
    }
    
  • Die meisten Eigenschaften sind Objekte der org.hibernate.mapping.SimpleValue-Typen. Unser interessanter Punkt ist die Methode SimpleValue.getType () - in dieser Methode wird definiert, welcher Typ verwendet wird, um Eigenschaftswerte während der Arbeit mit DB hin und her zu konvertieren

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

Zum jetzigen Zeitpunkt verstehe ich, dass ich BASIC_TYPES nicht ändern kann - dies ist die einzige Möglichkeit -, um das SimpleValue-Objekt durch die Eigenschaften von Java.util.Date-Typen in mein benutzerdefiniertes Objekt zu ersetzen, das den genauen zu konvertierenden Typ kennt.

Die Lösung:

  • Erstellen Sie eine benutzerdefinierte Containerentitäts-Manager-Factory, indem Sie die HibernatePersistence-Klasse erweitern und ihre Methode createContainerEntityManagerFactory überschreiben:

    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);
            }
        }
    }
    
  • Erstellen Sie ein Konfigurationsobjekt für den Ruhezustand, ändern Sie die Wertobjekte für die Java.util.Date-Eigenschaften und erstellen Sie dann eine benutzerdefinierte Entity Manager-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);
                    }
                }
            }
        }
    
    }
    

Wie Sie sehen können, iteriere ich einfach über jede Eigenschaft und ersetze UtilDateSimpleValue durch SimpleValue-Objekt für Eigenschaften vom Typ Java.util.Date. Dies ist eine sehr einfache Klasse - sie implementiert dieselbe Schnittstelle wie das SimpleValue-Objekt, z. B. org.hibernate.mapping.KeyValue. Im Konstruktor wird das ursprüngliche SimpleValue-Objekt übergeben. Daher wird jeder Aufruf von UtilDateSimpleValue mit einer Ausnahme an das ursprüngliche Objekt umgeleitet. Die Methode getType (...) gibt meinen benutzerdefinierten Typ zurück.

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;
    }
    ...
}
  • Und der letzte Schritt ist die Implementierung von UtilDateUserType. Ich erweitere einfach das ursprüngliche org.hibernate.type.TimestampType und überschreibe seine Methode get () wie folgt:

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

Das ist alles. Ein bisschen knifflig, aber jetzt wird jede Java.util.Date-Eigenschaft als Java.util.Date zurückgegeben, ohne dass der vorhandene Code geändert werden muss (Anmerkungen oder Setter werden geändert). Wie ich in Hibernate 4 oder höher herausgefunden habe, gibt es eine viel einfachere Möglichkeit, Ihren eigenen Typ zu ersetzen (siehe Details hier: Hibernate TypeResolver ). Anregungen oder Kritik sind willkommen.

9
dim1902

Eine einfache Alternative zur Verwendung eines benutzerdefinierten Benutzertyps besteht darin, im Setter ein neues Java.util.Date für die date -Eigenschaft in Ihrer persistierten Bean zu erstellen, z.

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

Die Ansätze 1 und 2 funktionieren offensichtlich nicht, da Sie Java.sql.Date - Objekte gemäß JPA/Hibernate-Spezifikation und nicht Java.util.Date Erhalten. Aus den Ansätzen 3 und 4 würde ich eher den letzteren wählen, da dieser aussagekräftiger ist und sowohl mit Feld- als auch Getter-Annotationen funktioniert.

Sie haben die Lösung 4 bereits in Ihrem referenzierten Blog-Beitrag dargestellt, wie @tscho freundlich hervorhob. Vielleicht sollte defaultForType (siehe unten) Ihnen die zentralisierte Lösung geben, nach der Sie gesucht haben. Natürlich muss immer noch zwischen Datumsfeldern (ohne Uhrzeit) und Zeitstempelfeldern unterschieden werden.

Zum späteren Nachschlagen lasse ich die Zusammenfassung der Verwendung Ihres eigenen Ruhezustands serType hier:

Damit Hibernate Ihnen Java.util.Date - Instanzen gibt, können Sie die Anmerkungen @ Type und @ TypeDef verwenden, um eine andere Zuordnung Ihres Java.util.Date = zu definieren Java tippt in und aus der Datenbank.

Siehe die Beispiele im Hauptreferenzhandbuch hier .

  1. Implementieren Sie ein serType , um die eigentliche Installation durchzuführen (Konvertierung nach/von Java.util.Date), z. TimestampAsJavaUtilDateType
  2. Fügen Sie eine @TypeDef-Annotation zu einer Entität oder zu einer package-info.Java hinzu - beide sind global für die Session Factory verfügbar (siehe obigen manuellen Link). Sie können defaultForType verwenden, um die Typkonvertierung auf alle zugeordneten Felder des Typs Java.util.Date Anzuwenden.

    @TypeDef
      name = "timestampAsJavaUtilDate",
      defaultForType = Java.util.Date.class, /* applied globally */
      typeClass = TimestampAsJavaUtilDateType.class
    )
    
  3. Optional können Sie anstelle von defaultForType Ihre Felder/Getter einzeln mit @Type versehen:

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

P.S. Um einen völlig anderen Ansatz vorzuschlagen: Wir vergleichen Date-Objekte normalerweise sowieso nicht mit equals (). Stattdessen verwenden wir eine Utility-Klasse mit Methoden zum Vergleichen von z.B. Nur das Kalenderdatum von zwei Datumsinstanzen (oder eine andere Auflösung wie Sekunden), unabhängig vom genauen Implementierungstyp. Das hat bei uns gut funktioniert.

5
Michael Paesold

Hier ist die Lösung für Hibernate 4.3.7.Final.

pacakge-info.Java enthält

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

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

Diese Lösung basiert hauptsächlich auf der TimestampType-Implementierung und fügt durch die anonyme Klasse des Typs JdbcTimestampTypeDescriptor zusätzliches Verhalten hinzu.

3
Ilya Bystrov

Es gibt einige Klassen in den Java Plattformbibliotheken, die eine instanziierbare Klasse erweitern und eine Wertekomponente hinzufügen. Beispielsweise erweitert Java.sql.Timestamp Java.util.Date und fügt ein Nanosekundenfeld hinzu. Die Equals-Implementierung für Timestamp verstößt gegen die Symmetrie und kann zu einem fehlerhaften Verhalten führen, wenn Timestamp- und Date-Objekte in derselben Auflistung verwendet oder auf andere Weise vermischt werden Solange Sie sie getrennt halten, hindert Sie nichts daran, sie zu mischen, und die daraus resultierenden Fehler können schwer zu beheben sein. Dieses Verhalten der Timestamp-Klasse war ein Fehler und sollte nicht emuliert werden.

besuche diesen Link

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

2
flexJavaMysql

Fügen Sie einfach diese Annotation @Temporal(TemporalType.DATE) für ein Java.util.Date - Feld in Ihrer Entitätsklasse hinzu.

Weitere Informationen finden Sie in this stackoverflow answer.

2
ZiOS

Ich hatte ein Problem damit, und meine JUnit-assertEquals konnten die von "Java.util.Date" ausgegebenen Datentypen nicht mit "Hibernate" vergleichen (wie in der Frage beschrieben handelt es sich tatsächlich um Zeitstempel). Es stellt sich heraus, dass durch Ändern der Zuordnung zu "Datum" anstelle von "Java.util.Date" im Ruhezustand Java.util.Date-Member generiert werden. Ich verwende eine XML-Zuordnungsdatei mit der Version 4.1.12 des Ruhezustands.

Diese Version gibt 'Java.util.Timestamp' aus:

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

Diese Version gibt 'Java.util.Date' aus:

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

Beachten Sie jedoch, dass bei Verwendung des Ruhezustands zum Generieren der DDL unterschiedliche SQL-Typen generiert werden (Datum für 'Datum' und Zeitstempel für 'Java.util.Date').

0
wbdarby