Dienstag, Mai 22, 2018

Hibernate und java.util.Date

In der Hibernate Welt geschehen manchmal unerklärliche Dinge. In diesem Fall war es der java.util.Date Typ einer Eigenschaft aus einer Hibernate Entity. Dieses Datumsfeld wurde für einen Vergleich mit einem anderen Datum herangezogen. Als Beispiel dient die Entity Person mit dem Datumsfeld #geburtstag. Die interessanten Dinge finden sich in (1) und (2). Bevor die Entity in der Datenbank angelegt wird, ist alles wie erwartet. Das java.util.Date ist ein java.util.Date. Der Vergleich liefert ebenfalls ein positives Ergebnis.

@Entity
@Table("PERSON")
public class Person {
    @Id
    @Column(name = "PERSON_ID")
    private Long personId;

    @Column(name = "NAME")
    private String name;

    @Column(name = "VORNAME")
    private String vorname;

    @Column(name = "GEBURTSTAG")
    private java.util.Date geburtstag;

    ...
}

final Date date = new DateTime(2018, 5, 22, 17, 0, 0).toDate();

Person person = new Person();
person.setGeburtstag(date);
person.setName("Name");
person.setVorname("Vorname");

// (1) Vergleich vor #save() und #refresh()
assertThat(person.getGeburtstag()).isInstanceOf(Date.class);
assertThat(person.getGeburtstag()).isNotInstanceOf(java.sql.Timestamp.class);
assertThat(person.getGeburtstag()).isEqualTo(date);

getSessionFactory().getCurrentSession().saveOrUpdate(person);
getSessionFactory().getCurrentSession().refresh(person);

// (2) Vergleich nach #save() und #refresh()
assertThat(person.getGeburtstag()).isInstanceOf(Date.class);
assertThat(person.getGeburtstag()).isInstanceOf(java.sql.Timestamp.class);
assertThat(person.getGeburtstag()).isNotEqualTo(date);
// IS NOT EQUAL, weil java.sql.Timestamp!
assertThat(now).isEqualTo(newRound.getDateTime());
// IS EQUAL! java.util.Date benutzt für den Vergleich long getTime()

Nach dem persistieren der Daten geschehen die bemerkenswerten Dinge. Aus dem java.util.Date wird ein java.sql.Timestamp. Das wäre nicht weiter tragisch, wenn nicht der Vergleich assertThat(person.getGeburtstag()).isNotEqualTo(now); positiv ausfallen würde. Was ist passiert? Mit dem refresh wurde die Entity Person neu aus der Datenbank gelesen, d.h. Hibernate liest per JDBC Verbindung die Daten aus der Datenbank. Für eine Timestamp Spalte lautet der entsprechende JDBC Typ java.sql.Timestamp, welcher von java.util.Date abgeleitet ist. In der #equals() Methode von java.sql.Timestamp findet sich der Hinweis:

true if the given Object is an instance of a Timestamp that is equal
to this Timestamp object; false otherwise

D.h. ein Vergleich von java.sql.Timestamp und java.util.Date ist immer false. Umgekehrt funktioniert der Vergleich: assertThat(now).isEqualTo(newRound.getDateTime());, da java.util.Date#equals() für den Vergleich auf das Ergebnis der Methode #getTime() zugreift. Das beste ist, man umgeht dieses Problem und verwendet gleich das java.time.LocalDateTime aus JDK 8 oder höher. Falls diese Option nicht besteht, empfiehlt sich für den Vergleich die #compare() Methode. java.sql.Timestamp implementiert #compare(Timestamp) und ein #compare(Date).

Keine Kommentare:

Kommentar veröffentlichen

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...