java - 在SQL数据库(例如H2)中插入和获取java.time.LocalDate对象

标签 java sql date jdbc h2

如何通过java.time插入和获取 LocalDate 类型(例如JDBC)到SQL数据库(例如H2 Database Engine)?

使用 PreparedStatement::setDate ResultSet::getDate 的旧方法适用于旧版 java.sql.Date 类型。我想避免使用这些麻烦的旧日期时间类。

通过JDBC driver发送java.time类型的现代方法是什么?

最佳答案

我们有两种通过JDBC交换java.time对象的途径:

  • 兼容JDBC 4.2的驱动程序如果您的JDBC驱动程序符合JDBC 4.2 specification或更高版本,则可以直接处理java.time对象。
  • JDBC 4.2之前的较旧驱动程序如果您的JDBC驱动程序尚未符合JDBC 4.2或更高版本,则可以将java.time对象简要地转换为它们的等效java.sql类型,反之亦然。查看添加到旧类中的新转换方法。

  • 传统的日期时间类(例如java.util.Datejava.util.Calendar)和相关的java.sql类(例如java.sql.Date)实在是一团糟。它们采用设计不当的黑客手段构建而成,被证明是有缺陷,麻烦且令人困惑的。尽可能避免使用它们。现在由java.time类取代。
    Table of date-time types in Java (both legacy and modern) and in standard SQL
    符合JDBC 4.2的驱动程序
    H2的内置JDBC驱动程序(自2017年3月起)似乎符合JDBC 4.2。
    兼容的驱动程序现在知道java.time类型。但是,JDBC委员会没有添加setLocalDate/getLocalDate各种方法,而是添加了 setObject / getObject 方法。
    要将数据发送到数据库,只需将java.time对象传递给PreparedStatement::setObject即可。驱动程序检测到传递的参数的Java类型,并将其转换为适当的SQL类型。将Java LocalDate转换为SQL DATE类型。有关这些映射的列表,请参见JDBC Maintenance Release 4.2 PDF文档的第22节。
    myPreparedStatement.setObject ( 1 , myLocalDate ); // Automatic detection and conversion of data type.
    
    要从数据库检索数据,请调用ResultSet::getObject。除了传递最终的Object对象,我们还可以传递一个额外的参数,即我们希望接收的数据类型的 Class 。通过指定期望的类,我们获得了type-safety和编译器检查并验证的IDE
    LocalDate localDate = myResultSet.getObject ( "my_date_column_" , LocalDate.class ); 
    
    这是一个完整的工作示例应用程序,显示了如何在H2数据库中插入和选择LocalDate值。
    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 ( );
            }
        }
    }
    
    运行时。

    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


    不合规的驱动程序
    对于H2,我建议您使用上面显示的代码。但是,仅供引用,对于其他尚不符合JDBC 4.2的数据库,我可以向您展示如何在java.time和java.sql类型之间进行简要转换。如下所示,这种转换代码当然可以在H2上运行,但是现在有了上面显示的更简单的方法,这样做是很愚蠢的。
    要将数据发送到数据库,请使用添加到该旧类的新方法将LocalDate转换为 java.sql.Date 对象。
    java.sql.Date mySqlDate = java.sql.Date.valueOf( myLocalDate );
    
    然后传递给PreparedStatement::setDate方法。
    preparedStatement.setDate ( 1, mySqlDate );
    
    要从数据库中检索,请调用ResultSet::getDate以获取java.sql.Date对象。
    java.sql.Date mySqlDate = myResultSet.getDate( 1 );
    
    然后立即将其转换为LocalDate。您应该尽可能简短地处理java.sql对象。仅使用java.time类型执行所有业务逻辑和其他工作。
    LocalDate myLocalDate = mySqlDate.toLocalDate();
    
    这是一个完整的示例应用程序,显示了H2数据库中java.sql类型与java.time类型的结合使用。
    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 ( );
            }
        }
    }
    
    为了好玩,让我们尝试另一个。这次从中获取连接的using a DataSource implementation。这次尝试使用 LocalDate.MIN ,它在ISO 8601(-999999999-01-01)中是大约十亿年前的常数。
    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 | date_: -999999999-01-01



    关于java.time
    java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如 java.util.Date Calendar SimpleDateFormat
    要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规范为JSR 310
    现在位于Joda-Time中的maintenance mode项目建议迁移到java.time类。
    您可以直接与数据库交换java.time对象。使用兼容JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。 Hibernate 5和JPA 2.2支持java.time。
    在哪里获取java.time类?
  • Java SE 8Java SE 9Java SE 10Java SE 11和更高版本-具有捆绑实现的标准Java API的一部分。
  • Java 9带来了一些次要功能和修复。

  • Java SE 6Java SE 7
  • 大部分java.time功能都在ThreeTen-Backport中反向移植到Java 6和7。

  • Android
  • 更高版本的Android(26+)捆绑了java.time类的实现。
  • 对于较早的Android(<26),API desugaring的过程带来了最初不是Android内置的subset of the java.time功能。
  • 如果讨价还价不能满足您的需求,则ThreeTenABP项目会将ThreeTen-Backport(如上所述)改编为Android。参见How to use ThreeTenABP…



  • ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如 Interval YearWeek YearQuarter more

    关于java - 在SQL数据库(例如H2)中插入和获取java.time.LocalDate对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43039614/

    相关文章:

    sql - 找出运行缓慢的存储过程

    java - 正确解析哪种 Java 日期格式?

    php - 如何从数据库中获取特定月份的数据?

    java - 如何创建分数列表?

    java - 我应该使用什么 ASM API?

    java - Spring JPA 使用单引号内的参数编写 native 查询

    mysql - 如何限制 LEFT JOIN 的结果

    sql - 需要根据当前日期选择重复项

    c# - LINQ to Entities - 比较日期

    java - 使用 java.io.file 在 Mac 中访问文件