2017-10-19 295 views
6

-509 VS 510

我看到了某种改变或错误的数据,与使用JDBC。所以我观察在Java 8 Update 151上使用H2 Database版本1.4.196。年由负-509改变到JDBC积极的510 H2数据库

这是一个完整的示例。

注意我们如何检索日期值三次,第一次作为一个LocalDate对象,其次为文本,第三从铸造LocalDate对象提取的int年数。在文本版中,我们可以看到这一年确实是负面的。神秘的LocalDate有不同的年份数字,它是积极的,而不是负面的。看起来像一个错误。

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(); 
    } 
} 

运行时。

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

ROW: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打交道时得到从-509转换为510,没有JDBC。

LD:-0509-01-01

ld.getYear():-509

我对H2的项目开了Issue ticket

+0

有趣的问题。我几乎不了解JDBC,但预感会发生某种形式的下溢。 –

+0

受启发[为什么查询日期BC更改为Java中的AD?](https://stackoverflow.com/questions/46835047/why-quering-a-date-bc-is-changed-to-ad-in- JAVA)?并不是说这个连接对这个问题很重要。 –

+0

对我来说听起来更像是一个忘记了忘记通过这个时代的错误。但这是猜测。 –

回答

1

Bug,fixed

此问题出自bug in H2

现已修复,截至2018-01。

3

该问题是由java.sql.Date转换为LocalDate 所致。 因为它是一个负年,Calendar实例保存所取得的结果将改变1 - 年,但转换为LocalDate Java时,不考虑附加信息(时代== BC),指示year < 0以下是返回结果前执行的最后一个方法。 enter image description here

试试这个:

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 


     } 
} 

感谢奥莱V.V.为您的评论!

+0

是的,但在这种情况下,它也将时代设置到BC,所以所有的信息仍然应该在那里以产生正确的'LocalDate'。必须有更多的事情发生,不能在那里?不过谢谢你的部分解释。 –

3

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,这是我们开始的一年。然而,片断输出:

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

第一行显示了Date确实包含一个负年,因为它应该(偏移转换为UTC改变从-509到-510的一年,我选择的是标准时间偏移我的电脑的时区设置)。该片段使用java.util.Date,但java.sql.Date继承了getYear方法,并且我也重现了与java.sql.Date类似的行为。

我做了一个简短的互联网搜索任何提及的可疑错误没有找到任何东西。我们可能想努力尝试。

+0

但为什么字符串值('rs.getString(2)')显示正确的负值年? –

+0

有趣的问题,@MickMnemonic。简单的答案是,它不会经历相同的(越野车)转换。我的猜测是字符串是在数据库端生成的,并以字符串形式从那里传递,但我当然不知道。 –