2016-06-12 38 views
3

我想将LocalDate存储在DATE列中并以未更改方式检索它。根据定义,DATELocalDate都是“本地”类型。因此,时区的概念不应以任何方式干涉。如何将sql日期映射到LocalDate

下面的代码是一个最小的示例,它在内存数据库中创建一个包含DATE列的表。 maven神器com.h2database:h2:1.4.192必须位于类路径中。

首先,定义方法insertretrieve

static void insert(DataSource ds, String date) throws SQLException { 
    try (Connection conn = ds.getConnection(); 
     Statement stmt = conn.createStatement()) { 
    stmt.execute("CREATE TABLE people (id BIGINT NOT NULL AUTO_INCREMENT" 
     + ", born DATE NOT NULL, PRIMARY KEY (id));"); 
    stmt.execute("INSERT INTO people (born) VALUES ('" + date + "')"); 
    } 
} 

static LocalDate retrieve(DataSource ds) throws SQLException { 
    try (Connection conn = ds.getConnection(); 
     Statement stmt = conn.createStatement(); 
     ResultSet rs = stmt.executeQuery("SELECT * FROM people limit 1")) { 
    if (rs.next()) { 
     java.sql.Date retrieved = java.sql.Date.valueOf(rs.getString("born")); 
     return retrieved.toLocalDate(); 
    } 
    throw new IllegalStateException("No data"); 
    } 
} 

注意,insert方法使用单引号LocalDatetoString值,所以没有机会为Java™创建时区歧义。现在每次打电话insert一次,然后多次retrieve,用不同的timzone设置:

public static void main(String[] args) throws Exception { 
    DataSource ds = JdbcConnectionPool.create("jdbc:h2:mem:test", "sa", "sa"); 
    LocalDate born = LocalDate.parse("2015-05-20"); 
    insert(ds, born.toString()); 
    System.out.println("Inserted: " + born); 
    for (int i : new int[]{-14, 0, 12}) { 
    TimeZone z = TimeZone.getTimeZone(String.format("Etc/GMT%+02d", i)); 
    TimeZone.setDefault(z); 
    System.out.println("Retrieved: " + retrieve(ds)); 
    } 
} 

则打印以下:

 
Inserted: 2015-05-20 
Retrieved: 2015-05-20 
Retrieved: 2015-05-19 
Retrieved: 2015-05-18 

怎么写retrieve方法,使其返回相同的值那是无条件插入,假设数据库表没有改变?

+0

你导入java.util.Date?在'retrieve ='行上,您可以在赋值左侧使用完全限定的'java.sql.Date',但不是正确的。 –

+1

你为什么期待3个相同的'LocalDate'被打印?你插入日期'2015-05-20'。然后,当您更改默认时区时,java.sql.Date.toLocalDate方法将返回不同的结果。 – Tunaki

+0

@BasilBourque固定 –

回答

3

我只是尝试了以下修改您的retrieve方法和它的工作对我来说:

H2 documentation for the DATE Type说,这是

日期数据类型。格式是yyyy-MM-dd。

因此,而不是你的......

java.sql.Date retrieved = (java.sql.Date) rs.getObject("born"); 
return retrieved.toLocalDate(); 

...我只是用...

return LocalDate.parse(rs.getString("born")); 

...和我的代码生成

Inserted: 2015-05-20 
Retrieved: 2015-05-20 
Retrieved: 2015-05-20 
Retrieved: 2015-05-20 
+0

看起来对我来说很安全,除了可能的空处理。谢谢!我仍然想知道如何用Oracle DB而不是H2(它在DATE中存储秒的精度)。不过,这将是一个不同的问题。 –

1

以下解决方案也适用。我更喜欢在接受的答案中通过String进行转换,因为它避免了下面显示的时区修补。然而,它可能在所有数据库上的工作方式不同,因为有些例如Oracle有不同的定义DATE

static LocalDate retrieve(DataSource ds) throws SQLException { 
    try (Connection conn = ds.getConnection(); 
     Statement stmt = conn.createStatement(); 
     ResultSet rs = stmt.executeQuery("SELECT * FROM people limit 1")) { 
    if (rs.next()) { 
     ZoneId utc = ZoneId.of("UTC"); 
     TimeZone z = TimeZone.getTimeZone(utc); 
     Calendar cal = Calendar.getInstance(z); 
     java.sql.Date retrieved = rs.getDate("born", cal); 
     long time = retrieved.getTime(); 
     java.util.Date utilDate = new java.util.Date(time); 
     Instant instant = utilDate.toInstant(); 
     ZonedDateTime zdt = instant.atZone(utc); 
     return zdt.toLocalDate(); 
    } 
    } 
    throw new IllegalStateException("No data"); 
} 

通过java.util.Date的转换在这个问题列出,如建议用户Tunaki:Missed opportunity to fix JDBC date handling in Java 8?