向/从SQL数据库(如H2)插入和获取java.time.LocalDate对象

Bas*_*que 6 java sql date jdbc h2

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

旧的方式使用PreparedStatement::setDateResultSet::getDate工作的遗留java.sql.Date类型.我想避免使用这些麻烦的旧日期时间类.

通过JDBC驱动程序发送java.time类型的现代方法是什么?

Bas*_*que 12

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

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

传统的日期时间类,如java.util.Date,java.util.Calendar和相关的java.sql类,如java.sql.Date一个可怕的混乱.它们采用设计糟糕的黑客攻击方式构建,已证明存在缺陷,麻烦和混乱.尽可能避免使用它们.现在取代了java.time类.

符合JDBC 4.2的驱动程序

H2的内置JDBC驱动程序(截至2017-03)似乎符合JDBC 4.2.

兼容的驱动程序现在知道java.time类型.但是,不是添加setLocalDate/ getLocalDate种类的方法,JDBC委员会添加了setObject/ getObject方法.

要将数据发送到数据库,只需将java.time对象传递给PreparedStatement::setObject.传递的参数的Java类型由驱动程序检测并转换为适当的SQL类型.Java LocalDate将转换为SQL DATE类型.有关这些映射的列表,请参见JDBC维护版本4.2 PDF文档的第22节.

myPreparedStatement.setObject ( 1 , myLocalDate ); // Automatic detection and conversion of data type.
Run Code Online (Sandbox Code Playgroud)

要从数据库中检索数据,请调用ResultSet::getObject.Object我们可以传递一个额外的参数,而不是强制生成的对象,Class我们期望接收的数据类型.通过指定预期的类,我们可以通过IDE和编译器检查并验证类型安全性.

LocalDate localDate = myResultSet.getObject ( "my_date_column_" , LocalDate.class ); 
Run Code Online (Sandbox Code Playgroud)

这是一个完整的工作示例应用程序,显示如何在LocalDateH2数据库中插入和选择值.

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 ( );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

跑步时

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上运行,如下所示,但这样做很愚蠢,因为我们现在有了更简单的方法.

要将数据发送到数据库,请使用添加到该旧类的新方法将您转换LocalDatejava.sql.Date对象.

java.sql.Date mySqlDate = java.sql.Date.valueOf( myLocalDate );
Run Code Online (Sandbox Code Playgroud)

然后传递给PreparedStatement::setDate方法.

preparedStatement.setDate ( 1, mySqlDate );
Run Code Online (Sandbox Code Playgroud)

要从数据库中检索,请调用ResultSet::getDate以获取java.sql.Date对象.

java.sql.Date mySqlDate = myResultSet.getDate( 1 );
Run Code Online (Sandbox Code Playgroud)

然后立即转换为LocalDate.您应该尽可能简短地处理java.sql对象.使用java.time类型完成所有业务逻辑和其他工作.

LocalDate myLocalDate = mySqlDate.toLocalDate();
Run Code Online (Sandbox Code Playgroud)

这是一个完整的示例应用程序,显示了在H2数据库中使用java.time类型的java.sql类型.

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 ( );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

为了好玩,让我们尝试另一个.这一次使用DataSource实现从中获取的连接.而这次尝试的LocalDate.MIN是大约10亿年前的常数,-999,999,999-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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

现在处于维护模式Joda-Time项目建议迁移到java.time类.

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.