我们有两种通过 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.Date、java.util.Calendar 等遗留日期时间类和java.sql.Date 等相关java.sql 类非常混乱。使用设计不佳的黑客方法构建,它们已被证明是有缺陷的、麻烦的和令人困惑的。尽可能避免使用它们。现在被 java.time 类所取代。
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。我们可以传递一个额外的参数,即我们期望接收的数据类型的Class,而不是强制转换生成的Object 对象。通过指定预期的类,我们获得type-safety 由您的IDE 和编译器检查和验证。
LocalDate localDate = myResultSet.getObject ( "my_date_column_" , LocalDate.class );
这是一个完整的工作示例应用程序,展示了如何将 LocalDate 值插入和选择到 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 ( );
}
}
}
运行时。
id_: e856a305-41a1-45fa-ab69-cfa676285461 |日期_:2017-03-26
id_:a4474e79-3e1f-4395-bbba-044423b37b9f |日期_:2017-03-27
id_: 5d47bc3d-ebfa-43ab-bbc2-7bb2313b33b0 |日期_: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 |日期_:-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 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。 Hibernate 5 & JPA 2.2 支持 java.time。
从哪里获取 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。