LocalDateTime 和 SQL 服务器 JDBC 4.2 驱动程序

LocalDateTime and SQL Server JDBC 4.2 driver

我正在尝试将新的 java.time 类 与最新版本的 Sql 服务器 JDBC 驱动程序一起使用。正如我所读,它应该只适用于方法:PreparedStatement.setObject()ResultSet.getObject().

所以我创建了示例代码,但无法使用 ResultSets。我不知道我在这里做错了什么。

Connection connection = DriverManager.getConnection(connectionString);
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM myTable WHERE ? BETWEEN date_from AND date_to");
preparedStatement.setObject(1, LocalDateTime.now());   // That works

ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
    Object o = resultSet.getObject("date_from"); 
    o.getClass() returns java.sql.Timestamp

    LocalDateTime dateTime = resultSet.getObject("date_from", LocalDateTime.class);
}

这会引发异常:

com.microsoft.sqlserver.jdbc.SQLServerException: The conversion to class java.time.LocalDateTime is unsupported.

驱动版本:mssql-jdbc-6.5.4.jre8-preview.jar

SQL服务器版本:2016


https://docs.microsoft.com/en-us/sql/connect/jdbc/jdbc-4-2-compliance-for-the-jdbc-driver?view=sql-server-2017

下面table这句话怎么解释:

新 Java 类 Java 8: LocalDate/LocalTime/LocalDateTime, OffsetTime/OffsetDateTime

新JDBC类型: TIME_WITH_TIMEZONE, TIMESTAMP_WITH_TIMEZONE, REF_CURSOR

REF_CURSOR is not supported in SQL Server. Driver throws a SQLFeatureNotSupportedException exception if this type is used. The driver supports all other new Java and JDBC type mappings as specified in the JDBC 4.2 specification.

这是因为 resultSet.getObject(...) 的 Microsoft SQL Server JDBC 驱动程序实现无法自动从 java.sql.Timestamp 转换为 LocalDateTime

作为变通方法,您可以获取 java.sql.Timestamp 的值,然后使用以下方法将 java.sql.Timestamp 转换为 LocalDateTimejava.sql.Timestamp.toLocalDateTime()

LocalDateTime dateTime = resultSet.getTimestamp("date_from").toLocalDateTime()

看起来 mssql-jdbc 驱动程序没有完全实现 JDBC 4.2 中指定的 java.time 支持。它不支持 ResultSet.getObject,但它支持 PreparedStatement.setObject

如 Loc Le 的回答所建议的解决方法是检索为 Timestamp 并将其转换为 LocalDateTime.

I don't know what I'm doing wrong here.

你没有做错任何事。在 version 7.1.0, discussed here.

之前,您遇到了 SQL 服务器的 Microsoft JDBC 驱动程序的缺陷

如果您使用的是 mssql-jdbc 版本 7.1.0 或更高版本,那么您可以按预期使用 getObject(x, LocalDateTime.class)

对于 7.1.0 之前的 mssql-jdbc 版本,正如其他人所建议的,您需要检索 Timestamp 并将其转换为 LocalDateTime。但是,请注意简单的解决方案...

LocalDateTime dateTime = resultSet.getTimestamp("date_from").toLocalDateTime()

... 如果 JVM 的默认时区遵守夏令时 a.k.a,将破坏某些 date/time 值。 "Summer Time"。例如,

// time zone with Daylight Time
TimeZone.setDefault(TimeZone.getTimeZone("America/Edmonton"));

// test environment
Statement st = conn.createStatement();
st.execute("CREATE TABLE #tmp (id INT PRIMARY KEY, dt2 DATETIME2)");
st.execute("INSERT INTO #tmp (id, dt2) VALUES (1, '2018-03-11 02:00:00')");
ResultSet rs = st.executeQuery("SELECT dt2 FROM #tmp WHERE id=1");
rs.next();

// test code
LocalDateTime x = rs.getTimestamp("dt2").toLocalDateTime();  // bad

System.out.println(x.toString());

将打印“2018-03-11T03:00”。请注意,时间是“03:00”,而不是“02:00”。

相反,您需要将 Timestamp 检索为 UTC,然后将其转换为 UTC 的 LocalDateTime,从而删除时区组件

// time zone with Daylight Time
TimeZone.setDefault(TimeZone.getTimeZone("America/Edmonton"));

// test environment
Statement st = conn.createStatement();
st.execute("CREATE TABLE #tmp (id INT PRIMARY KEY, dt2 DATETIME2)");
st.execute("INSERT INTO #tmp (id, dt2) VALUES (1, '2018-03-11 02:00:00')");
ResultSet rs = st.executeQuery("SELECT dt2 FROM #tmp WHERE id=1");
rs.next();

// test code
Timestamp ts = getTimestamp("dt2", Calendar.getInstance(TimeZone.getTimeZone("UTC")));
LocalDateTime x = LocalDateTime.ofInstant(ts.toInstant(), ZoneId.of("UTC"));  // good

System.out.println(x.toString());

打印“2018-03-11T02:00”。

如果您遇到困难并想尝试“老办法”:

这里有一个等价转换的简单例子。测试你自己。

@Test
public void testingConversionTimestampAndLocalDateTime(){
    Timestamp initialTimestamp = new Timestamp(System.currentTimeMillis());

    LocalDateTime localDateTime = LocalDateTime.ofInstant(initialTimestamp.toInstant(), ZoneId.of("America/Sao_Paulo"));

    ZoneOffset zoneOffset = ZoneId.of("America/Sao_Paulo").getRules().getOffset(localDateTime);
    Timestamp timestamp = Timestamp.from(localDateTime.toInstant(zoneOffset));

    Timestamp timestamp1 = Timestamp.valueOf(localDateTime);

    assertEquals(initialTimestamp, timestamp);
    assertEquals(initialTimestamp, timestamp1);
}