Was ist der moderne Weg zum Senden von java.time-Typen über a JDBC-Treiber?
Basil Bourque
Wir haben zwei Wege zum Austausch von java.time-Objekten über JDBC:
JDBC 4.2-kompatible Treiber Wenn Ihr JDBC-Treiber mit der JDBC 4.2-Spezifikation oder später können Sie direkt mit den java.time-Objekten umgehen.
Ältere Treiber, vor JDBC 4.2 Wenn Ihr JDBC-Treiber noch nicht mit JDBC 4.2 oder höher kompatibel ist, konvertieren Sie kurz Ihre java.time-Objekte in ihren entsprechenden java.sql-Typ oder umgekehrt. Suchen Sie nach neuen Konvertierungsmethoden, die den alten Klassen hinzugefügt wurden.
Die Legacy-Datum-Uhrzeit-Klassen wie z java.util.Date, java.util.Calendarund die verwandten java.sql Klassen wie z java.sql.Date sind ein furchtbares Durcheinander. Sie wurden mit einem schlecht konzipierten Hacking-Ansatz erstellt und haben sich als fehlerhaft, lästig und verwirrend erwiesen. Vermeiden Sie sie, wann immer es möglich ist. Jetzt ersetzt durch die java.time-Klassen.
JDBC 4.2-kompatible Treiber
Der integrierte JDBC-Treiber für H2 (Stand 2017-03) scheint JDBC 4.2 zu entsprechen.
Kompatible Treiber kennen jetzt die java.time-Typen. Aber anstatt hinzuzufügen setLocalDate/getLocalDate Arten von Methoden, fügte das JDBC-Komitee hinzu setObject/getObject Methoden.
Um Daten an die Datenbank zu senden, übergeben Sie einfach Ihr java.time-Objekt an PreparedStatement::setObject. Der Java-Typ Ihres übergebenen Arguments wird vom Treiber erkannt und in den entsprechenden SQL-Typ konvertiert. Ein Java LocalDate wird in ein SQL konvertiert DATE Art. Siehe Abschnitt 22 der JDBC-Wartungsversion 4.2 PDF-Dokument für eine Liste dieser Zuordnungen.
myPreparedStatement.setObject ( 1 , myLocalDate ); // Automatic detection and conversion of data type.
Um Daten aus der Datenbank abzurufen, rufen Sie an ResultSet::getObject. Anstatt das Ergebnis zu werfen Object Objekt können wir ein zusätzliches Argument übergeben, das Class des Datentyps, den wir erwarten. Durch die Angabe der erwarteten Klasse gewinnen wir Typsicherheit geprüft und verifiziert durch Ihre IDE und Compiler.
Für H2 ist der oben gezeigte Code der Weg, den ich Ihnen empfehle. Aber zu Ihrer Information, für andere Datenbanken, die JDBC 4.2 noch nicht entsprechen, kann ich Ihnen kurz zeigen, wie Sie zwischen java.time- und java.sql-Typen konvertieren. Diese Art von Konvertierungscode läuft sicherlich auf H2, wie ich unten zeige, aber dies zu tun, ist jetzt albern, da wir den oben gezeigten einfacheren Ansatz haben.
Um Daten an die Datenbank zu senden, konvertieren Sie Ihre LocalDate zu einem java.sql.Date Objekt mit neuen Methoden, die dieser alten Klasse hinzugefügt wurden.
Dann sofort in a umwandeln LocalDate. Sie sollten die java.sql-Objekte so kurz wie möglich behandeln. Erledigen Sie Ihre gesamte Geschäftslogik und andere Arbeiten nur mit den java.time-Typen.
LocalDate myLocalDate = mySqlDate.toLocalDate();
Hier ist eine vollständige Beispiel-App, die diese Verwendung von java.sql-Typen mit java.time-Typen in einer H2-Datenbank zeigt.
package com.example.h2localdate;
import java.sql.*;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.UUID;
/**
* Hello world!
*/
public class App {
public static void main ( String[] args ) {
App app = new App ( );
app.doIt ( );
}
private void doIt ( ) {
try {
Class.forName ( "org.h2.Driver" );
} catch ( ClassNotFoundException e ) {
e.printStackTrace ( );
}
try (
Connection conn = DriverManager.getConnection ( "jdbc:h2:mem:trash_me_db_" ) ;
Statement stmt = conn.createStatement ( ) ;
) {
String tableName = "test_";
String sql = "CREATE TABLE " + tableName + " (\n" +
" id_ UUID DEFAULT random_uuid() PRIMARY KEY ,\n" +
" date_ DATE NOT NULL\n" +
");";
stmt.execute ( sql );
// Insert row.
sql = "INSERT INTO test_ ( date_ ) " + "VALUES (?) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement ( sql ) ; ) {
LocalDate today = LocalDate.now ( ZoneId.of ( "America/Montreal" ) );
preparedStatement.setDate ( 1, java.sql.Date.valueOf ( today.minusDays ( 1 ) ) ); // Yesterday.
preparedStatement.executeUpdate ( );
preparedStatement.setDate ( 1, java.sql.Date.valueOf ( today ) ); // Today.
preparedStatement.executeUpdate ( );
preparedStatement.setDate ( 1, java.sql.Date.valueOf ( today.plusDays ( 1 ) ) ); // Tomorrow.
preparedStatement.executeUpdate ( );
}
// Query all.
sql = "SELECT * FROM test_";
try ( ResultSet rs = stmt.executeQuery ( sql ) ; ) {
while ( rs.next ( ) ) {
//Retrieve by column name
UUID id = ( UUID ) rs.getObject ( "id_" ); // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
java.sql.Date sqlDate = rs.getDate ( "date_" );
LocalDate localDate = sqlDate.toLocalDate (); // Immediately convert into java.time. Mimimize use of java.sql types.
//Display values
System.out.println ( "id_: " + id + " | date_: " + localDate );
}
}
} catch ( SQLException e ) {
e.printStackTrace ( );
}
}
}
Probieren wir zum Spaß noch eins aus. Diesmal Verwendung einer DataSource Implementierung von wo aus man eine Verbindung bekommt. Und diesmal versuchen LocalDate.MIN was vor etwa einer Milliarde Jahren in ISO 8601, -999999999-01-01 eine Konstante ist.
package work.basil.example;
import java.sql.*;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.UUID;
public class LocalDateMin
{
public static void main ( String[] args )
{
LocalDateMin app = new LocalDateMin();
app.doIt();
}
private void doIt ()
{
org.h2.jdbcx.JdbcDataSource ds = new org.h2.jdbcx.JdbcDataSource();
ds.setURL( "jdbc:h2:mem:localdate_min_example_db_;DB_CLOSE_DELAY=-1" );
ds.setUser( "scott" );
ds.setPassword( "tiger" );
try (
Connection conn = ds.getConnection() ;
Statement stmt = conn.createStatement() ;
)
{
String tableName = "test_";
String sql = "CREATE TABLE " + tableName + " (\n" +
" id_ UUID DEFAULT random_uuid() PRIMARY KEY ,\n" +
" date_ DATE NOT NULL\n" +
");";
stmt.execute( sql );
// Insert row.
sql = "INSERT INTO test_ ( date_ ) " + "VALUES (?) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; )
{
LocalDate today = LocalDate.now( ZoneId.of( "America/Montreal" ) );
preparedStatement.setObject( 1 , LocalDate.MIN ); // MIN =
preparedStatement.executeUpdate();
}
// Query all.
sql = "SELECT * FROM test_";
try ( ResultSet rs = stmt.executeQuery( sql ) ; )
{
while ( rs.next() )
{
//Retrieve by column name
UUID id = rs.getObject( "id_" , UUID.class ); // Pass the class to be type-safe, rather than casting returned value.
LocalDate localDate = rs.getObject( "date_" , LocalDate.class ); // Ditto, pass class for type-safety.
//Display values
System.out.println( "id_: " + id + " | date_: " + localDate );
}
}
} catch ( SQLException e )
{
e.printStackTrace();
}
}
}
Weitere Informationen finden Sie unter Oracle-Tutorial. Und durchsuchen Sie Stack Overflow nach vielen Beispielen und Erklärungen. Spezifikation ist JSR310.
Sie dürfen umtauschen java.time Objekte direkt mit Ihrer Datenbank. Benutze einen JDBC-Treiber gemäß JDBC 4.2 oder später. Keine Notwendigkeit für Saiten, keine Notwendigkeit für java.sql.* Klassen. Unterstützung für Hibernate 5 und JPA 2.2 java.time.
Spätere Versionen von Android (26+) bündeln Implementierungen der java.time Klassen.
Für frühere Android-Versionen (<26) ist der Prozess von API-Entzuckerung bringt ein Teilmenge der java.time Funktionen, die ursprünglich nicht in Android integriert waren.
Wenn die Entzuckerung nicht das bietet, was Sie brauchen, die ThreeTenABP Projekt passt ThreeTen-Backport (oben erwähnt) auf Android. Sehen Wie benutzt man ThreeTenABP….
Die ThreeTen-Extra project erweitert java.time um zusätzliche Klassen. Dieses Projekt ist ein Testfeld für mögliche zukünftige Ergänzungen zu java.time. Sie können hier einige nützliche Klassen finden, wie z Interval, YearWeek, YearQuarterund mehr.
Auch bei ‘kompatiblem Treiber’ befindet sich das gespeicherte Datum immer noch in java.sql.date, nicht in LocalDate. Wenn Sie LocalDate.MIN in Ihrer Tabelle speichern, würde der Abruf mit dem Datum „id_: b8d9be62-71cc-4ea9-86e2-cb55138379d1 |“ enden Datum_: +169087565-03-15′
– Guangliang
1. April 2018 um 1:57 Uhr
@Guangliang Nein, ich sehe dein Problem nicht. Beim Speichern und Abrufen bekomme ich den gleichen Wert LocalDate.MIN: -999999999-01-01. Sehen Sie sich den Quellcode der neuen Beispiel-App mit an LocalDate.MIN die ich meiner Antwort angehängt habe.
– Basilikum Bourque
27. Februar 2019 um 23:13 Uhr
Für mich die setObject hat das Problem, dass irgendwo eine ungewollte Zeitzonenkonvertierung drin ist – das durch das LocalDate-Objekt repräsentierte Datum wird um einen Tag nach hinten verschoben (ich befinde mich in der Zeitzone GMT+1)! Ist das ein Zeichen für einen nicht konformen Treiber? Oder sollten nicht konforme Treiber überhaupt keine setObject-Methode haben?
– Kodierung
7. März 2019 um 7:23 Uhr
@codeling Nein, gibt es Nein eine solche Anpassung wird mit vorgenommen LocalDate Objekte werden in einem SQL-Standard gespeichert DATE Spalte eingeben. Wenn Sie den 23. Januar 2019 speichern, erhalten Sie absolut dasselbe zurück. Sie denken vielleicht an das Schreckliche java.sql.Date Klasse welche vorgibt um nur ein Datum darzustellen, trägt aber tatsächlich eine Uhrzeit und UTC. Verwenden Sie niemals diese Legacy-Klasse. Nur benutzen java.time Klassen für alle Ihre Date-to-E-Handhabung. Wenn Sie ein rätselhaftes Verhalten feststellen, posten Sie eine neue Frage mit Beispielcode.
– Basilikum Bourque
7. März 2019 um 16:21 Uhr
Ja, mein Fehler – ich habe übersehen, dass die Datenbankspalte ein DATETIME-Typ war, kein DATE. Entschuldigung für den Lärm!
– Kodierung
7. März 2019 um 16:32 Uhr
9329400cookie-checkEinfügen und Abrufen von java.time.LocalDate-Objekten in/aus einer SQL-Datenbank wie H2yes