Insérer et récupérer java.temps.LocalDate objets vers / à partir d'une base de données SQL telle que H2


Comment insérer et récupérer java.temps types tels que LocalDate via JDBC une base de données SQL comme le H2 Moteur de Base de données?

À L'ancienne à l'aide de PreparedStatement::setDate et ResultSet::getDate travaille pour l'héritage java.sql.Date type. Je veux éviter d'utiliser ces anciennes classes de date-heure gênantes.

Quelle est la façon moderne d'envoyer java.types de temps par un pilote JDBC ?

Author: Mark Rotteveel, 2017-03-27

1 answers

Nous avons deux voies pour échanger java.objets temporels via JDBC:

  • Pilotes compatibles JDBC 4.2
    Si votre pilote JDBC est conforme à la spécification JDBC 4.2 ou ultérieure, vous pouvez traiter directement avec java.le temps des objets.
  • Pilotes plus anciens, avant JDBC 4.2
    Si votre pilote JDBC n'est pas encore conforme à JDBC 4.2 ou version ultérieure, vous convertissez brièvement votre java.objets time à leur équivalent java.type sql ou vice-versa. Regarder aux nouvelles méthodes de conversion ajoutées aux anciennes classes.

L'héritage de date-heure classes telles que java.util.Date, java.util.Calendar, et les java.sql classes telles que java.sql.Date sont un désordre indescriptible. Construits avec une approche piratée mal conçue, ils se sont avérés défectueux, gênants et déroutants. Éviter autant que possible. Maintenant supplanté par le java.les classes de temps.

Les pilotes compatibles JDBC 4.2

Le pilote JDBC intégré pour H2 (à partir de 2017-03) semble être conforme à JDBC 4.2.

Les pilotes compatibles connaissent maintenant java.les types d'heures. Mais plutôt que de les ajouter setLocalDate/getLocalDate sortes de méthodes, JDBC comité a ajouté setObject/getObject des méthodes.

Pour envoyer des données à la base de données, passez simplement votre java.objet temporel à PreparedStatement::setObject. Le type Java de votre argument passé est détecté par le pilote et converti au type SQL approprié. Un Java LocalDate est converti en un type SQL DATE. Voir la section 22 de la version de maintenance de JDBC 4.2 Document PDF pour une liste de ces mappages.

myPreparedStatement.setObject ( 1 , myLocalDate ); // Automatic detection and conversion of data type.

Pour récupérer les données de la base de données, appelez ResultSet::getObject. Plutôt que de lancer l'objet Object résultant, nous pouvons passer un argument supplémentaire, le Class du type de données que nous nous attendons à recevoir. En spécifiant la classe attendue, nous gagnons type-safetyvérifié et vérifié par votre {[72] }E et compilateur.

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

Voici un exemple d'application complet montrant comment insérer et sélectionner des valeurs LocalDate dans une base de données H2.

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

Lors de l'exécution.

Id_: e856a305-41a1-45fa-ab69-cfa676285461 | date_: 2017-03-26

Id_: a4474e79-3e1f-4395-bbba-044423b37b9f | date_: 2017-03-27

Id_: 5d47bc3d-ebfa-43ab-bbc2-7bb2313b33b0 | date_: 2017-03-28

Pilotes non conformes

Pour H2, le code indiqué ci-dessus est la route que je vous recommande de prendre. Mais pour info, pour d'autres bases de données qui ne sont pas encore conformes à JDBC 4.2, je peux vous montrer comment convertir brièvement entre java.du temps et de java.les types sql. Ce type de code de conversion fonctionne certainement sur H2 comme je le montre ci-dessous, mais cela est stupide maintenant que nous avons l'approche plus simple montrée ci-dessus.

Pour envoyer des données à la base de données, convertissez votre LocalDate en un java.sql.Date objet utilisant de nouvelles méthodes ajoutées à cette ancienne classe.

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

Puis passez à la méthode PreparedStatement::setDate.

preparedStatement.setDate ( 1, mySqlDate );

Pour récupérer de la base de données, appelez ResultSet::getDate pour obtenir un objet java.sql.Date.

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

Alors convertir immédiatement en un LocalDate. Vous devriez gérer le java.objets sql aussi brièvement que possible. Faites toute votre logique métier et tout autre travail en utilisant uniquement java.les types d'heures.

LocalDate myLocalDate = mySqlDate.toLocalDate();

Voici un exemple complet d'application montrant cette utilisation de java.types sql avec java.types de temps dans une base de données H2.

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

À propos de java.temps

Le java.time framework est intégré à Java 8 et versions ultérieures. Ces classes supplantent l'ancien héritage date-heure les classes telles que java.util.Date, Calendar, & SimpleDateFormat.

Le projet

Joda-Time, maintenant en mode maintenance, conseille la migration vers le java.temps classes.

Pour en savoir plus, voir le Tutoriel Oracle. Et rechercher Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310.

Où obtenir le java.les classes de temps?

Le Trois-Extra le projet étend java.temps avec des cours supplémentaires. Ce projet est un terrain d'essai pour d'éventuels ajouts futurs à java.temps. Vous pouvez trouver quelques classes utiles ici comme Interval, YearWeek, YearQuarter, et plus.

 6
Author: Basil Bourque, 2017-05-23 12:10:17