Einfügen und Abrufen von java.time.LocalDate-Objekten in/aus einer SQL-Datenbank wie H2

Lesezeit: 7 Minuten

Einfugen und Abrufen von javatimeLocalDate Objekten inaus einer SQL Datenbank wie H2
Basil Bourque

Wie man einfügt und holt java.time Arten wie LocalDate über JDBC zu einer SQL-Datenbank wie z H2-Datenbank-Engine?

Die alte Art zu verwenden PreparedStatement::setDate und ResultSet::getDate arbeitet für das Erbe java.sql.Date Art. Ich möchte vermeiden, diese lästigen alten Datums- und Uhrzeitklassen zu verwenden.

Was ist der moderne Weg zum Senden von java.time-Typen über a JDBC-Treiber?

Einfugen und Abrufen von javatimeLocalDate Objekten inaus einer SQL Datenbank wie H2
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.

Tabelle der Datums- und Uhrzeittypen in Java (sowohl Legacy als auch Modern) und in Standard-SQL

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.

LocalDate localDate = myResultSet.getObject ( "my_date_column_" , LocalDate.class ); 

Hier ist eine vollständige funktionierende Beispiel-App, die zeigt, wie man einfügt und auswählt LocalDate Werte in eine H2-Datenbank.

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.setObject ( 1, today.minusDays ( 1 ) );  // Yesterday.
                preparedStatement.executeUpdate ( );
                preparedStatement.setObject ( 1, today );                  // Today.
                preparedStatement.executeUpdate ( );
                preparedStatement.setObject ( 1, 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 = 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 ( );
        }
    }
}

Beim Laufen.

id_: e856a305-41a1-45fa-ab69-cfa676285461 | Datum_: 2017-03-26

id_: a4474e79-3e1f-4395-bbba-044423b37b9f | Datum_: 2017-03-27

id_: 5d47bc3d-ebfa-43ab-bbc2-7bb2313b33b0 | Datum_: 2017-03-28

Nicht konforme Treiber

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.

java.sql.Date mySqlDate = java.sql.Date.valueOf( myLocalDate );

Dann weiter zum PreparedStatement::setDate Methode.

preparedStatement.setDate ( 1, mySqlDate );

Um aus der Datenbank abzurufen, rufen Sie an ResultSet::getDate ein erhalten java.sql.Date Objekt.

java.sql.Date mySqlDate = myResultSet.getDate( 1 );

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

id_: 4b0ba138-d7ae-469b-854f-5cbe7430026f | Datum_: -999999999-01-01


Über java.time

Die java.time Framework ist in Java 8 und höher integriert. Diese Klassen ersetzen die lästigen alten Vermächtnis Datum-Zeit-Klassen wie z java.util.Date, Calendar& SimpleDateFormat.

Weitere Informationen finden Sie unter Oracle-Tutorial. Und durchsuchen Sie Stack Overflow nach vielen Beispielen und Erklärungen. Spezifikation ist JSR310.

Die Joda-Zeit Projekt, jetzt in Wartungsmodusrät zur Migration in die java.time Klassen.

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.

Wo erhalte ich die java.time-Klassen?

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

932940cookie-checkEinfügen und Abrufen von java.time.LocalDate-Objekten in/aus einer SQL-Datenbank wie H2

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy