it-swarm.com.de

Oracle SQL DATE-Konvertierungsproblem mit iBATIS über Java JDBC

Ich ringe derzeit mit einem Oracle SQL DATE-Konvertierungsproblem mit iBATIS von Java.

Ich verwende den Oracle JDBC Thin-Treiber ojdbc14 Version 10.2.0.4.0. iBATIS Version 2.3.2. Java 1.6.0_10-rc2-b32.

Das Problem dreht sich um eine Spalte vom Typ DATE, die von diesem SQL-Snippet zurückgegeben wird:

SELECT *
FROM   TABLE(pk_invoice_qry.get_contract_rate(?,?,?,?,?,?,?,?,?,?)) order by from_date

Der Aufruf der Paketprozedur gibt einen Ref-Cursor zurück, der in eine TABELLE eingeschlossen wird, in die die Ergebnismenge dann einfach gelesen werden kann, als wäre es eine Auswahlabfrage für eine Tabelle.

In PL/SQL Developer ist eine der zurückgegebenen Spalten FROM_DATE vom Typ SQL DATE auf die Uhrzeit genau:

Tue Dec 16 23:59:00 PST 2008

Wenn ich jedoch über iBATIS und JDBC darauf zugreife, bleibt der Wert nur auf den Tag genau:

Tue Dec 16 12:00:00 AM PST 2008

Dies wird klarer, wenn es so angezeigt wird:

Gewesen sein sollte:

1229500740000 milliseconds since Epoch
Tuesday, December 16, 2008 11:59:00 PM PST

Aber dies stattdessen bekommen:

1229414400000 milliseconds since Epoch
Tuesday, December 16, 2008 12:00:00 AM PST
(as instance of class Java.sql.Date)

Egal, was ich versuche, ich kann nicht die volle Genauigkeit dieser DATE-Spalte offenlegen, die über Java JDBC und iBATIS zurückgegeben wird.

Was iBATIS abbildet, ist folgendes:

FROM_DATE : 2008-12-03 : class Java.sql.Date

Das aktuelle iBATIS-Mapping lautet:

<result property="from_date" jdbcType="DATE" javaType="Java.sql.Date"/>

Ich habe auch versucht:

<result property="from_date" jdbcType="DATETIME" javaType="Java.sql.Date"/>

oder

<result property="from_date" jdbcType="TIMESTAMP" javaType="Java.sql.Timestamp"/>

Alle versuchten Zuordnungen liefern jedoch denselben abgeschnittenen Datumswert. Es ist, als ob JDBC den Schaden bereits angerichtet hätte, bevor iBATIS ihn überhaupt berührt.

Offensichtlich verliere ich einen Teil meiner Datengenauigkeit, indem ich JDBC und iBATIS durchlaufe, was nicht der Fall ist, wenn ich in PL/SQL Developer bleibe und das gleiche SQL-Snippet wie ein Testskript ausführe. Überhaupt nicht akzeptabel, sehr frustrierend und letztendlich sehr beängstigend.

13
RogerV

Die vollständigen Informationen (und sie sind komplexer als hier beschrieben und hängen möglicherweise davon ab, welche bestimmte Version der Oracle-Treiber verwendet wird) finden Sie in Richard Yees Antwort hier - [jetzt abgelaufener Link zu Nabble].


Schnelles Greifen, bevor es vom Knabbern abläuft ...

Roger, siehe: http://www.Oracle.com/technetwork/database/enterprise-edition/jdbc-faq-090281.html#08_01

Im Einzelnen: Einfache Datentypen Was ist mit DATE und TIMESTAMP los? Dieser Abschnitt befasst sich mit einfachen Datentypen. :-)

Vor Version 9.2 haben die Oracle-JDBC-Treiber den DATE-SQL-Typ Java.sql.Timestamp zugeordnet. Dies ergab einen gewissen Sinn, da der Oracle DATE SQL-Typ wie Java.sql.Timestamp sowohl Datums- als auch Zeitinformationen enthält. Die offensichtlichere Zuordnung zu Java.sql.Date war etwas problematisch, da Java.sql.Date keine Zeitinformationen enthält. Es war auch der Fall, dass das RDBMS den SQL-Typ TIMESTAMP nicht unterstützte, sodass beim Zuordnen von DATE zu Timestamp keine Probleme auftraten.

In 9.2 wurde TIMESTAMP-Unterstützung zum RDBMS hinzugefügt. Der Unterschied zwischen DATE und TIMESTAMP besteht darin, dass TIMESTAMP Nanosekunden enthält und DATE nicht. Ab 9.2 wird DATE dem Datum und TIMESTAMP dem Zeitstempel zugeordnet. Leider liegt ein Problem vor, wenn Sie sich darauf verlassen, dass DATE-Werte Zeitinformationen enthalten.

Es gibt verschiedene Möglichkeiten, um dieses Problem zu beheben:

Ändern Sie Ihre Tabellen, um TIMESTAMP anstelle von DATE zu verwenden. Dies ist wahrscheinlich selten möglich, aber die beste Lösung, wenn dies der Fall ist.

Ändern Sie Ihre Anwendung so, dass sie defineColumnType verwendet, um die Spalten als TIMESTAMP und nicht als DATE zu definieren. Es gibt Probleme damit, weil Sie defineColumnType nur dann wirklich verwenden möchten, wenn Sie dies müssen (siehe Was ist defineColumnType und wann soll ich es verwenden?).

Ändern Sie Ihre Anwendung, um getTimestamp anstelle von getObject zu verwenden. Dies ist nach Möglichkeit eine gute Lösung. Viele Anwendungen enthalten jedoch generischen Code, der auf getObject basiert, sodass dies nicht immer möglich ist.

Legen Sie die Verbindungseigenschaft V8Compatible fest. Dadurch werden die JDBC-Treiber angewiesen, die alte Zuordnung anstelle der neuen zu verwenden. Sie können dieses Flag entweder als Verbindungseigenschaft oder als Systemeigenschaft festlegen. Sie legen die Verbindungseigenschaft fest, indem Sie sie dem Java.util.Properties-Objekt hinzufügen, das an DriverManager.getConnection oder an OracleDataSource.setConnectionProperties übergeben wird. Sie legen die Systemeigenschaft fest, indem Sie die Option -D in Ihre Java-Befehlszeile einfügen.

Java -Doracle.jdbc.V8Compatible = "true" MyApp Oracle JDBC 11.1 behebt dieses Problem. Ab dieser Version ordnet der Treiber standardmäßig SQL-DATE-Spalten Java.sql.Timestamp zu. Es ist nicht erforderlich, V8-kompatibel einzustellen, um das richtige Mapping zu erhalten. V8Compatible ist stark veraltet. Sie sollten es überhaupt nicht verwenden. Wenn Sie es auf true setzen, wird nichts geschadet, aber Sie sollten es nicht mehr verwenden.

Obwohl dies nur selten der Fall war, bestand V8Compatible nicht darin, das Problem von DATE to Date zu beheben, sondern die Kompatibilität mit 8i-Datenbanken zu unterstützen. 8i-Datenbanken (und ältere) unterstützen den TIMESTAMP-Typ nicht. Das Setzen von V8Compatible bewirkte nicht nur, dass SQL DATE beim Lesen aus der Datenbank einem Zeitstempel zugeordnet wurde, sondern auch, dass alle Zeitstempel beim Schreiben in die Datenbank in SQL DATE konvertiert wurden. Da 8i nicht unterstützt wird, unterstützen die 11.1 JDBC-Treiber diesen Kompatibilitätsmodus nicht. Aus diesem Grund wird V8Compatible nicht unterstützt.

Wie oben erwähnt, konvertieren die 11.1-Treiber beim Lesen aus der Datenbank standardmäßig SQL DATE in Timestamp. Dies war immer das Richtige und die Änderung in 9i war ein Fehler. Die 11.1-Treiber haben das richtige Verhalten wiederhergestellt. Auch wenn Sie in Ihrer Anwendung nicht V8-kompatibel eingestellt haben, sollten Sie in den meisten Fällen keinen Unterschied im Verhalten feststellen. Möglicherweise stellen Sie einen Unterschied fest, wenn Sie getObject zum Lesen einer DATE-Spalte verwenden. Das Ergebnis ist eher ein Zeitstempel als ein Datum. Da Timestamp eine Unterklasse von Date ist, ist dies im Allgemeinen kein Problem. Möglicherweise stellen Sie einen Unterschied fest, wenn Sie sich beim Abschneiden der Zeitkomponente auf die Konvertierung von DATE in Date verlassen oder den Wert eingeben. Ansonsten sollte die Änderung transparent sein.

Wenn Ihre App aus irgendeinem Grund sehr empfindlich auf diese Änderung reagiert und Sie lediglich das 9i-10g-Verhalten haben müssen, können Sie eine Verbindungseigenschaft festlegen. Setzen Sie mapDateToTimestamp auf false und der Treiber kehrt zum Standardverhalten von 9i-10g zurück und ordnet DATE auf Date zu.

Wenn möglich, sollten Sie Ihren Spaltentyp in TIMESTAMP anstelle von DATE ändern.

-Richard


Roger Voss hat geschrieben: Ich habe die folgende Frage/das folgende Problem zum Stackoverflow gestellt. Wenn also jemand eine Lösung kennt, ist es gut, wenn sie dort beantwortet wird:

Oracle SQL DATE-Konvertierungsproblem mit iBATIS über Java JDBC

Hier ist die Problembeschreibung:

Ich ringe derzeit mit einem Oracle SQL DATE-Konvertierungsproblem mit iBATIS von Java.

Ich verwende den Oracle JDBC Thin-Treiber ojdbc14 Version 10.2.0.4.0. iBATIS Version 2.3.2. Java 1.6.0_10-rc2-b32.

Das Problem dreht sich um eine Spalte vom Typ DATE, die von diesem SQL-Snippet zurückgegeben wird:

SELECT * FROM TABLE (pk_invoice_qry.get_contract_rate (?,?,?,?,?,?,?,?,?,?)) Order by from_date

Der Aufruf der Paketprozedur gibt einen Ref-Cursor zurück, der in eine TABELLE eingeschlossen wird, in die die Ergebnismenge dann einfach gelesen werden kann, als wäre es eine Auswahlabfrage für eine Tabelle.

In PL/SQL Developer ist eine der zurückgegebenen Spalten FROM_DATE vom Typ SQL DATE auf die Uhrzeit genau:

Tue Dec 16 23:59:00 PST 2008

Wenn ich jedoch über iBATIS und JDBC darauf zugreife, bleibt der Wert nur auf den Tag genau:

Tue Dec 16 12:00:00 AM PST 2008

Dies wird klarer, wenn es so angezeigt wird:

Sollte gewesen sein: 1229500740000 Millisekunden seit Epoche Dienstag, 16. Dezember 2008 11:59:00 PM PST

Aber dies stattdessen erhalten: 1229414400000 Millisekunden seit Epoche Dienstag, 16. Dezember 2008, 00:00 Uhr PST (als Instanz der Klasse Java.sql.Date)

Egal, was ich versuche, ich kann nicht die volle Genauigkeit dieser DATE-Spalte offenlegen, die über Java JDBC und iBATIS zurückgegeben wird.

Was iBATIS abbildet, ist folgendes:

FROM_DATE: 2008-12-03: Klasse Java.sql.Date

Das aktuelle iBATIS-Mapping lautet:

Ich habe auch versucht:

oder

Alle versuchten Zuordnungen liefern jedoch denselben abgeschnittenen Datumswert. Es ist, als ob JDBC den Schaden bereits angerichtet hätte, bevor iBATIS ihn überhaupt berührt.

Offensichtlich verliere ich einen Teil meiner Datengenauigkeit, indem ich über JDBC und iBATIS gehe, was nicht der Fall ist, wenn ich in PL/SQL Developer bleibe und das gleiche SQL-Snippet als Testskript ausführe. Überhaupt nicht akzeptabel, sehr frustrierend und letztendlich sehr beängstigend.

8
Gwyn Evans

Ich fand heraus, wie man dieses Problem löst. iBATIS ermöglicht die Registrierung von benutzerdefinierten Handlern. Also habe ich in meiner Datei sqlmap-config.xml Folgendes hinzugefügt:

<typeAlias alias="OracleDateHandler" type="com.tideworks.ms.CustomDateHandler"/>
<typeHandler callback="OracleDateHandler" jdbcType="DATETIME" javaType="date"/>

Und dann diese Klasse hinzugefügt, die die iBATIS TypeHandlerCallback-Schnittstelle implementiert:

// corrected getResult()/setParameter() to correctly deal with when value is null
public class CustomDateHandler implements TypeHandlerCallback {
    @Override
    public Object getResult(ResultGetter getter) throws SQLException {
        final Object obj = getter.getTimestamp();
        return obj != null ? (Date) obj : null;
    }

    @Override
    public void setParameter(ParameterSetter setter,Object value) throws SQLException {
        setter.setTimestamp(value != null ? new Timestamp(((Date)value).getTime()) : null);
    }

    @Override
    public Object valueOf(String datetime) {
        return Timestamp.valueOf(datetime);
    }
}

Wann immer ich ein Oracle DATE abbilden muss, beschreibe ich es jetzt so:

<result property="from_date" jdbcType="DATETIME" javaType="date"/>
6
RogerV

Ich habe mein Problem mit jdbcType = "TIMESTAMP" anstelle von jdbcType = "DATE" gelöst

• PROBLEM:

• Gelöst:

Grüße.

Pedro

2
Pedro Calvo

Das Problem liegt beim Oracle-Treiber.

Die beste Lösung, die ich gefunden habe, war, alle jdbcType = "DATE" zu jdbcType = "TIMESTAMP" und alle #column_name: DATE # zu #column_name: TIMESTAMP # zu ändern.

Also ändere:

<result property="from_date" jdbcType="DATE" javaType="Java.sql.Date"/>

zu

<result property="from_date" jdbcType="TIMESTAMP" javaType="Java.sql.Date"/>
1
bob

Richard Yee erwähnt, dass die neuesten Treiber von Oracle das Problem beheben. Das kann ich bestätigen. Hatte hier das gleiche Problem mit 10.2-Treibern, wurde heute auf ojdbc5.jar (11.2.0.1.0) aktualisiert und das Problem ist jetzt behoben.

0
wallenborn

Ja, ich verstehe - der einfache SQL-DATE-Standard muss sein, nur die Tagesauflösung zu speichern. In der Tat ist hier ein Ausschnitt des Oracle-Typs DATE:

Oracle unterstützt sowohl Datum als auch Uhrzeit, jedoch anders als der SQL2-Standard. Anstatt zwei separate Entitäten, Datum und Uhrzeit, zu verwenden, verwendet Oracle nur eine, DATE. Der DATE-Typ wird in einem speziellen internen Format gespeichert, das nicht nur Monat, Tag und Jahr, sondern auch Stunde, Minute und Sekunde enthält.

Was darauf hinweist, dass das DATE von Oracle das Standard-SQL-DATE überschreitet.

Hmm, Oracle PL/SQL-Benutzer verwenden DATE häufig, um Werte zu speichern, bei denen es auf die sekundengenaue Auflösung ankommt. Sieht so aus, als ob iBATIS so etwas wie das Hibernate-SQL-Dialektkonzept benötigt, bei dem anstelle der Interpretation von DATE über Java.sql.Date Java.util.Date überschrieben und stattdessen interpretiert werden könnte, das Javadocs als Auflösung in Millisekunden zulässt.

Leider, als ich das Mapping in etwas geändert habe:

<result property="from_date" jdbcType="DATE" javaType="Java.util.Date"/>

oder

<result property="from_date" jdbcType="DATETIME" javaType="Java.util.Date"/>

Es ist immer noch anscheinend zuerst das SQL-Datum in ein Java.sql.Date übersetzt und die Tageszeitgenauigkeit verloren.

0
RogerV

Das Problem ist die Verwendung von Java.sql.Date. Gemäß Javadoc müssen die von einer Java.sql.Date -Instanz umschlossenen Millisekundenwerte "normalisiert" werden, indem die Stunden, Minuten, Sekunden und Millisekunden in der bestimmten Zeitzone, mit der die Instanz verknüpft ist, auf Null gesetzt werden, um der Norm zu entsprechen die Definition von SQL DATE.

0
ninesided