![](/img/trans.png)
[英]JPA EclipseLink PostgreSQL Insert java.time.LocalDate as java.sql.Date inserts wrong date
[英]Insert & fetch java.time.LocalDate objects to/from an SQL database such as H2
如何通過JDBC向 SQL 數據庫(如H2 數據庫引擎)插入和獲取java.time類型(如LocalDate
?
使用PreparedStatement::setDate
和ResultSet::getDate
的舊方法適用於遺留的java.sql.Date
類型。 我想避免使用這些麻煩的舊日期時間類。
通過JDBC 驅動程序發送 java.time 類型的現代方法是什么?
我們有兩種途徑可以通過 JDBC 交換 java.time 對象:
遺留的日期時間類如java.util.Date
、 java.util.Calendar
和相關的java.sql
類如java.sql.Date
是一團糟。 它們采用設計不佳的黑客方法構建,已被證明存在缺陷、麻煩且令人困惑。 盡可能避免它們。 現在被 java.time 類取代。
H2 的內置 JDBC 驅動程序(截至 2017-03)似乎符合 JDBC 4.2。
兼容驅動程序現在知道 java.time 類型。 但是,JDBC 委員會並沒有添加setLocalDate
/ getLocalDate
方法,而是添加了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和編譯器檢查和驗證的類型安全性。
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 ( );
}
}
}
為了好玩,讓我們嘗試另一個。 這次使用DataSource
實現從中獲取連接。 這一次嘗試LocalDate.MIN
,它是大約 10 億年前 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 8 及更高版本中。 這些類取代麻煩的老傳統日期時間類,如java.util.Date
, Calendar
,和SimpleDateFormat
。
要了解更多信息,請參閱Oracle 教程。 並在 Stack Overflow 上搜索許多示例和解釋。 規范是JSR 310 。
現在處於維護模式的Joda-Time項目建議遷移到java.time類。
您可以直接與您的數據庫交換java.time對象。 使用符合JDBC 4.2或更高版本的JDBC 驅動程序。 不需要字符串,不需要java.sql.*
類。 Hibernate 5 & JPA 2.2 支持java.time 。
從哪里獲得 java.time 類?
ThreeTen-Extra項目用額外的類擴展了 java.time。 該項目是未來可能添加到 java.time 的試驗場。 您可以在這里找到一些有用的類,比如Interval
, YearWeek
, YearQuarter
,和更多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.