2017-02-21 3 views
1

Wir haben eine Spring + Hibernate Java 7 Web-Anwendung. Derzeit verwenden wir .hbm.xml Hibernate-Dateien (anstelle der allgemeineren bekannten Annotationen) als Zuordnung zwischen den Java-Entitäten und den Datenbanktabellen.Oracle getTime() keep Millisekunden

Einer unserer Java Entitätsklassen hat eine java.util.Date millisekundengenau:

public class SomeEntity extends AbstractEntity{ 
    ... 

    private java.util.Date someDate; // with ms precision 

    ... 
} 

In unserer .hbm.xml Datei haben wir die zu diesem Zeitpunkt:

... 
<hibernate-mapping> 
    <class name="com.namespace.Entity" table="entity" batch-size="10"> 
    ... 
    <property name="SomeDate" column="somedate" type="com.namespace.CustomDateType" /> 
    ... 

, die zu einer Datenbanktabelle verknüpft ist mit folgender Zeile:

SOMEDATE TIMESTAMP(2) NULLABLE=Yes 

O ur CustomDateType Klasse:

package com.namespace.type; 

import java.io.Serializable; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 
import java.sql.Timestamp; 
import java.sql.Types; 
import java.util.Date; 

import org.hibernate.usertype.UserType; 

public class CustomDateType implements UserType { 

    private static final int[] SQL_TYPES = { Types.TIMESTAMP }; 

    @Override 
    public Object assemble(final Serializable serializable, final Object o) { 
    return deepCopy(serializable); 
    } 

    @Override 
    public Object deepCopy(final Object value) { 
    return value; 
    } 

    @Override 
    public Serializable disassemble(final Object o) { 
    return (Serializable) deepCopy(o); 
    } 

    @Override 
    public boolean equals(final Object x, final Object y) { 
    if (x == y) { 
     return true; 
    } else if ((x == null) || (y == null)) { 
     return false; 
    } 
    return x.equals(y); 
    } 

    @Override 
    public int hashCode(final Object o) { 
    return ((CustomDateType) o).hashCode(); 
    } 

    @Override 
    public boolean isMutable() { 
    return false; 
    } 

    @Override 
    public Object nullSafeGet(final ResultSet resultSet, final String[] names, final Object owner) throws SQLException { 
    Date result = null; 
    final Timestamp timestamp = resultSet.getTimestamp(names[0]); // This is where the problem is 
    if (!resultSet.wasNull()) { 
     result = new Date(timestamp.getTime()); 
    } 
    return result; 
    } 

    @Override 
    public void nullSafeSet(final PreparedStatement statement, final Object value, final int index) throws SQLException { 
    final Date dateToSet = (Date) value; 
    statement.setTimestamp(index, dateToSet == null ? null : new Timestamp(dateToSet.getTime())); 
    } 

    @Override 
    public Object replace(final Object o, final Object o1, final Object o2) { 
    return o; 
    } 

    @Override 
    public Class<Date> returnedClass() { 
    return Date.class; 
    } 

    @Override 
    public int[] sqlTypes() { 
    return SQL_TYPES; 
    } 
} 

Wenn someDate Debuggen tatsächlich Millisekunden in der Java-Klasse hat. Aber in der CustomDateType#nullSafeGet Methode hat das resultierende TimeStamp keine Millisekunden mehr.

Der resultSet Parameter ist die oracle.jdbc.driver.ForwardOnlyResultSet, die in oracle.jdbc.driver.T4CPreparedStatement weiter, die die Klasse & Verfahren weiterhin in denen der Kern des Problems ist: oracle.jdbc.driver.DateTimeCommonAccessor#getTime(int, Calendar)

In diesem Verfahren wird die milliseconds abgezogen werden und Sie sind mit second links - Präzision.

Gibt es eine Möglichkeit, die Millisekunden in dieser Situation zu halten? Wie kann man Millisekunden-Genauigkeit sparen, wenn man von java.util.Date zur Datenbank TIMESTAMP -type column geht?

HINWEIS: Die Millisekunden sind bereits verschwunden, bevor Sie in die Datenbanktabelle gehen. Wenn ich einen Breakpoint bei final Timestamp timestamp = resultSet.getTimestamp(names[0]); setze und übergehe, hat das resultierende timestamp keine Millisekunden mehr.


EDIT: Der Inhalt des hava.util.Date Objekt und java.sql.Timestamp Objekte während der Fehlersuche:

enter image description here enter image description here

Die fraction und millis beide entnommen und auf 0 ..

+1

sollten Sie 'java.sql.Timestamp' für Timestamp-Spalten verwenden. Nicht 'java.util.Date' –

+0

Warum erhalten Sie zuerst einen' java.sqlTimestamp' und dann einen 'java.sql.Time'. Beachten Sie, dass 'java.sql.Time' eine Genauigkeit in Sekunden, nicht in Millisekunden hat (beachten Sie, dass die Dokumentation nicht vollständig klar ist, müssen Sie zwischen den Zeilen für diese Einschränkung lesen) java.sql.Timestamp'. –

+0

@MarkRotteveel Hmm ok, ich verstehe zwar, dass 'Time' und' Timestamp' unterschiedliche Genauigkeit haben, und dieser Oracle JDBC 'getTime' Aufruf ist die Wurzel des Problems. Aber wenn ich streng auf unseren eigenen Code schaue, sehe ich 'Timestamp timestamp = resultSet.getTimeStamp (names [0]);'. Danach werden die Dinge von 'oracle.jdbc' aufgerufen, und aus irgendeinem Grund endet es bei' DateTimeCommonAccessor # getTime (...) 'anstelle von' getTimestamp (...) '. Ich werde untersuchen, wo es vom ursprünglichen getTimestamp in getTime übergeht, aber haben Sie vielleicht eine Idee, wo dies passieren könnte, und noch wichtiger: warum/wie? –

Antwort

2

A java.sql.Timestamp speichert zweite Genauigkeit in der java.util.DatefastTime Feld (anstelle von Millisekunden für eine normale java.util.Date), und es hat ein separates nanos Feld für Sub-Sekunden-Genauigkeit. Wenn Sie java.sql.Timestamp.getTime() aufrufen, kombiniert es diese zwei in einem Millisekunden-Genauigkeitswert neu.

Da Sie Ihr Feld als TIMESTAMP(2) deklariert haben, bedeutet dies, dass Sie nur 2 Dezimalstellen mit einer Genauigkeit von unter einer Sekunde haben. Wenn Sie versuchen, 00:00:00.001 zu speichern, was tatsächlich gespeichert wird, ist 00:00:00:00, und das ist auch, was Sie wieder abrufen.Mit anderen Worten, Sie müssen die Genauigkeit erhöhen, indem Sie das Feld als deklarieren (oder sogar höher, bis zu 9 wird unterstützt), oder Sie müssen mit der reduzierten Genauigkeit von zwei Dezimalstellen leben.

Beachten Sie, dass, wenn Sie einen höher als millisekundengenau verwenden (so mehr als 3 Dezimalstellen), können Sie nur, dass die vollständige Präzision erhalten java.sql.Timestamp.getNanos() verwenden, die die volle Unter zweite Präzision enthält (die nanos nur die Unter zweite Fraktion und sollte immer weniger als 1 Sekunde betragen) oder durch Umwandlung der Timestamp in eine java.time.LocalDateTime.