簡體   English   中英

向/從 SQL 數據庫(如 H2)插入和獲取 java.time.LocalDate 對象

[英]Insert & fetch java.time.LocalDate objects to/from an SQL database such as H2

如何通過JDBC向 SQL 數據庫(如H2 數據庫引擎)插入和獲取java.time類型(如LocalDate

使用PreparedStatement::setDateResultSet::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.Datejava.util.Calendar和相關的java.sql類如java.sql.Date是一團糟。 它們采用設計不佳的黑客方法構建,已被證明存在缺陷、麻煩且令人困惑。 盡可能避免它們。 現在被 java.time 類取代。

Java(舊版和現代版)和標准 SQL 中的日期時間類型表

符合 JDBC 4.2 的驅動程序

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.time框架內置於 Java 8 及更高版本中。 這些類取代麻煩的老傳統日期時間類,如java.util.DateCalendar ,和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 的試驗場。 您可以在這里找到一些有用的類,比如IntervalYearWeekYearQuarter ,和更多

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM