使用 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 【问题描述】:
-509
与 510
我看到使用 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 得知,驱动程序确实使用了一个或多个旧日期和时间类,至少是Calendar
和GregorianCalendar
。从Calendar
到LocalDate
还有更多的转化路径,所以通过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”