使用 H2 数据库的 JDBC 中的年份从负 -509 变为正 510

Posted

技术标签:

【中文标题】使用 H2 数据库的 JDBC 中的年份从负 -509 变为正 510【英文标题】:Year changing from negative -509 to a positive 510 in JDBC with H2 Database 【发布时间】:2018-04-01 01:40:22 【问题描述】:

-509510

我看到使用 JDBC 时发生了某种更改或错误的数据。所以我观察到在 Java 8 Update 151 上使用 H2 Database 版本 1.4.196。

这是一个完整的例子。

请注意我们如何三次检索日期值,第一次作为LocalDate 对象,第二次作为文本,第三次作为从转换LocalDate 对象中提取的int 年份数字。在文本版本中,我们可以看到年份确实是负数。神秘地LocalDate 有一个不同的年份数字,它是正数而不是负数。好像是个bug。

private void doIt ( )

    System.out.println( "BASIL - Running doIt." );
    try
    
        Class.forName( "org.h2.Driver" );
     catch ( ClassNotFoundException e )
    
        e.printStackTrace( );
    

    try (
            Connection conn = DriverManager.getConnection( "jdbc:h2:mem:" ) ;  // Unnamed throw-away in-memory database.
    )
    
        conn.setAutoCommit( true );
        String sqlCreate = "CREATE TABLE history  ( id IDENTITY , when  DATE ); ";
        String sqlInsert = "INSERT INTO history ( when ) VALUES ( ? ) ; ";
        String sqlQueryAll = "SELECT * FROM history  ; ";

        PreparedStatement psCreate = conn.prepareStatement( sqlCreate );

        psCreate.executeUpdate( );

        PreparedStatement psInsert = conn.prepareStatement( sqlInsert );

        psInsert.setObject( 1 , LocalDate.of( 2017 , 1 , 23 ) );
        psInsert.executeUpdate( );

        psInsert.setObject( 1 , LocalDate.of( -509 , 1 , 1 ) );
        psInsert.executeUpdate( );

        PreparedStatement psQueryAll = conn.prepareStatement( sqlQueryAll );
        ResultSet rs = psQueryAll.executeQuery( );
        while ( rs.next( ) )
        
            long l = rs.getLong( 1 );  // Identity column.
            // Retrieve the same date value in three different ways.
            LocalDate ld = rs.getObject( 2 , LocalDate.class );  // Extract a `LocalDate`, and implicitly call its `toString` method that uses standard ISO 8601 formatting.
            String s = rs.getString( 2 );  // Extract the date value as text from the database using the database-engine’s own formatting.
            int y = ( ( LocalDate ) rs.getObject( 2 , LocalDate.class ) ).getYear( );  // Extract the year number as an integer from a `LocalDate` object.
            String output = "ROW: " + l+ " | " + ld + " | when as String: " + s+ " | year: " + y ;
            System.out.println( output );
        

        conn.close( );
     catch ( SQLException e )
    
        e.printStackTrace( );
    

运行时。

行:1 | 2017-01-23 |当字符串:2017-01-23 |年份:2017

行:2 | 0510-01-01 |当字符串:-509-01-01 |年份:510

所以似乎涉及到 JDBC。请注意年份是如何显示为正 510 而不是负 509。我不理解这种行为。

我可以推断这是JDBC 内部的问题,而不是LocalDate 内部的问题。看到这个example code run live in IdeOne.com 表明LocalDate 对象确实携带并报告了负年份。

LocalDate ld = LocalDate.of( -509 , 1 , 1 ) ;
System.out.println( "ld.toString(): " + ld ) ;
System.out.println( "ld.getYear(): " + ld.getYear() ) ;

请注意,在仅处理 LocalDate 而没有 JDBC 时,我们如何将 -509 转换为 510。

ld:-0509-01-01

ld.getYear(): -509

我在 H2 项目上开了一个Issue ticket 。

【问题讨论】:

有趣的问题。我对 JDBC 几乎一无所知,但预感会发生某种形式的下溢。 灵感来自Why quering a date BC is changed to AD in Java??并不是说这种联系对这个问题很重要。 对我来说听起来更像是一个错误,有人忘记了传递这个时代。但这是猜测。 另见related issue on GitHub。 此问题has been fixed in the H2 source 应包含在 1.4.196 之后的版本中。 【参考方案1】:

问题是由java.sql.Date 转换为LocalDate 引起的 . 因为它是负年份,所以持有获取结果的 Calendar 实例会将 year 转换为 1 - year 但在转换为 LocalDate java 时不会考虑额外的信息(era==BC)表示year < 0下面是返回结果前最后执行的方法。

试试这个:

public class Test 
 public static void main(String[] args) 

            Calendar instance = Calendar.getInstance();
            instance.set(-509,1,1);

            java.sql.Date d = new Date(instance.getTime().getTime());

            System.out.println(d.toLocalDate().getYear());// 510


        

感谢 Ole V.V.给你意见!!!

【讨论】:

是的,但在这种情况下,它也会将时代设置为 BC,因此所有信息仍应保留在那里以生成正确的 LocalDate。一定有更多的事情发生,不是吗?谢谢你的部分解释。【参考方案2】:

TL;DR:如果 JDBC 驱动程序在内部使用 java.sql.Date 并使用 java.sql.Date.toLocalDate() 对其进行转换,则已弃用的 Date.getYear() 中的可疑错误将(至少有时)会导致该行为你观察到了。

这是猜测,但我发现它很有趣,可以分享。

我从SEY_91’s answer 得知,驱动程序确实使用了一个或多个旧日期和时间类,至少是CalendarGregorianCalendar。从CalendarLocalDate还有更多的转化路径,所以通过java.sql.Date的转化路径只是其中之一。不过,其他转化路径可能会遇到相同的错误。

事实:toLocalDate 方法依赖于已弃用的 getYear 方法。来源:

@SuppressWarnings("deprecation")
public LocalDate toLocalDate() 
    return LocalDate.of(getYear() + 1900, getMonth() + 1, getDate());

为了了解getYear 在普通时代前一年的表现,我尝试了:

    OffsetDateTime dateTimeBce 
            = OffsetDateTime.of(-509, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(1));
    Date d = Date.from(dateTimeBce.toInstant());
    System.out.println("d.toInstant() " + d.toInstant());
    System.out.println("d.getYear() (deprecated): " + d.getYear() 
                        + ", means " + (d.getYear() + 1900));

由于来自getYear 的年份是“基于 1900 年的”,因此预期年份为 -2409。如果我们将 1900 添加到此,我们将得到 -509,即我们开始的年份。但是,sn-p 打印:

d.toInstant() -0510-12-31T23:00:00Z
d.getYear() (deprecated): -1390, means 510

第一行显示Date 确实包含负年份(偏移转换为 UTC 将年份从 -509 更改为 -510;我选择了计算机时区设置的标准时间偏移)。 sn-p 使用java.util.Date,但java.sql.Date 继承了getYear 方法,我也用java.sql.Date 重现了类似的行为。

我在 Internet 上进行了简短搜索,以查找任何提及可疑错误的内容,但没有找到任何内容。我们可能想更加努力。

【讨论】:

但是为什么字符串值 (rs.getString( 2 )) 显示正确的负年份? 有趣的问题,@MickMnemonic。简单的答案是它没有经历相同的(错误的)转换。我的猜测是字符串是在数据库端生成并从那里作为字符串传递的,但我当然不知道。【参考方案3】:

错误,已修复

这个问题是由bug in H2 引起的。

现在已修复,截至 2018 年 1 月。

【讨论】:

以上是关于使用 H2 数据库的 JDBC 中的年份从负 -509 变为正 510的主要内容,如果未能解决你的问题,请参考以下文章

内存数据库中的 H2:使用 JDBC 设置时区? Java 单元测试

尝试使用 jdbc、SimpleDateFormat 插入数据库时​​,日期中的年份不正确

org.h2.jdbc.JdbcSQLSyntaxErrorException h2 数据库 java

org.h2.jdbc.JdbcSQLException:在使用 H2 数据库进行测试期间未找到列“Id”

micronaut 中的 H2 默认 jdbc url 是啥

为啥返回 org.h2.jdbc.JdbcSQLException?