向/从SQL数据库(例如H2)插入和获取java.time.LocalDate对象
如何通过JDBC将诸如LocalDate
类的java.time类型插入到SQL数据库(如H2数据库引擎)中 ?
使用PreparedStatement::setDate
和ResultSet::getDate
的旧方法适用于旧的java.sql.Date
类型。 我想避免使用这些麻烦的旧日期时间类。
通过JDBC驱动程序发送java.time类型的现代方法是什么?
我们有两条通过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
非常混乱。 它们采用设计糟糕的黑客攻击方式构建,已被certificate存在缺陷,麻烦和混乱。 尽可能避免使用它们。 现在取代了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.
要从数据库中检索数据,请调用ResultSet::getObject
。 我们可以传递一个额外的参数,即我们期望接收的数据类型的Class
,而不是生成生成的Object
对象。 通过指定期望的类,我们可以通过IDE和编译器检查并validation类型安全性 。
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.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 ( ); } } }
关于java.time
java.time框架内置于Java 8及更高版本中。 这些类取代了麻烦的旧遗留日期时间类,如java.util.Date
, Calendar
和SimpleDateFormat
。
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
要了解更多信息,请参阅Oracle教程 。 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310 。
从哪里获取java.time类?
- Java SE 8和SE 9及更高版本
- 内置。
- 带有捆绑实现的标准Java API的一部分。
- Java 9增加了一些小function和修复。
- Java SE 6和SE 7
- 许多java.timefunction都被反向移植到ThreeTen-Backport中的 Java 6和7。
- Android的
- ThreeTenABP项目特别适用于Android的ThreeTen-Backport (如上所述)。
- 请参见如何使用ThreeTenABP ….
ThreeTen-Extra项目使用其他类扩展了java.time。 该项目是未来可能添加到java.time的试验场。 您可以在这里找到一些有用的课程,如Interval
, YearWeek
, YearQuarter
等。