提问人:Randy B 提问时间:2/10/2023 最后编辑:AnonymousRandy B 更新时间:2/11/2023 访问量:456
使用 Timestamp.from(Instant) 时读取本地时区的 Java PreparedStatement
Java PreparedStatement reading local timezone when using Timestamp.from(Instant)
问:
我正在尝试将 UTC 时间戳填充到 SQL 表中,但是当我用于获取当前 UTC 时间时,转换是将本地时区写入表中。有没有办法将UTC写入表中?Instant.now()
Timestamp.from(instant)
PreparedStatement ps = connection.prepareStatement(...)
ps.setString(1, Timestamp.from(Instant.now())
这会导致本地时区与 UTC 相反。
JDBC 驱动程序是 。net.sourceforge.jtds.jdbc.Driver
答:
OffsetDateTime
当我使用 Instant.now() 获取当前 UTC 时间时,
不要用于 SQL 数据库工作。Instant
在 JDBC 4.2+ 中,规范将类映射到类似于 SQL 标准类型的列。OffsetDateTime
TIMESTAMP WITH TIME ZONE
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
在 JDBC 中既不映射也不映射。SQL 标准没有定义与这些类等效的此类类型。Instant
ZonedDateTime
顺便说一句,对于类似于 SQL 标准类型的列 ,请使用类。TIMESTAMP WITHOUT TIME ZONE
LocalDateTime
避免使用旧版日期时间类
Timestamp.from(instant) 的转换
永远不要使用可怕的旧日期时间类,例如 .仅使用它们的替代品:JSR 310 中定义的现代 java.time 类。Timestamp
写入数据库:
myPreparedStatement.setObject … , odt ) ;
取回:
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
不依赖于默认区域
您评论道:
问题是我的JVM默认为本地时间
您应该以不关心 JVM 当前默认时区的方式编写 Java 代码。
上面显示的代码不受以下因素的影响 JVM 的当前默认时区。
示例代码
这是一个完整的示例。
package work.basil.example.db;
import javax.sql.DataSource;
import java.sql.*;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;
public class DemoH2InMem
{
public static void main ( String[] args )
{
DemoH2InMem app = new DemoH2InMem();
app.demo();
}
private void demo ( )
{
DataSource dataSource = this.fetchDataSource();
this.createTable( dataSource );
this.insertDummyData( dataSource );
this.dump( dataSource );
// Scanner scanner = new Scanner( System.in );
// System.out.print( "Type anything to end program: " );
// String anything = scanner.nextLine();
System.out.println( "Demo done at " + Instant.now() );
}
private DataSource fetchDataSource ( )
{
org.h2.jdbcx.JdbcDataSource dataSource = new org.h2.jdbcx.JdbcDataSource();
dataSource.setURL( "jdbc:h2:mem:demo_db;DB_CLOSE_DELAY=-1" );
return dataSource;
}
private void createTable ( final DataSource dataSource )
{
String sql =
"""
CREATE TABLE bogus_ (
id_ UUID PRIMARY KEY ,
when_ TIMESTAMP WITH TIME ZONE
)
;
""";
try (
Connection conn = dataSource.getConnection() ;
Statement stmt = conn.createStatement() ;
)
{
stmt.execute( sql );
}
catch ( SQLException e ) { throw new RuntimeException( e ); }
}
private void insertDummyData ( final DataSource dataSource )
{
String sql =
"""
INSERT INTO bogus_ ( id_ , when_ )
VALUES ( ? , ? )
;
""";
try (
Connection conn = dataSource.getConnection() ;
PreparedStatement pstmt = conn.prepareStatement( sql ) ;
)
{
pstmt.setObject( 1 , UUID.fromString( "97a9e379-4d8f-4d06-8bea-43560a72120b" ) );
pstmt.setObject( 2 , OffsetDateTime.now( ZoneOffset.UTC ) );
pstmt.executeUpdate();
pstmt.setObject( 1 , UUID.fromString( "052ae129-d0ca-4fdf-9a06-c87d20a2d3f2" ) );
pstmt.setObject( 2 , OffsetDateTime.now( ZoneOffset.UTC ) );
pstmt.executeUpdate();
}
catch ( SQLException e ) { throw new RuntimeException( e ); }
}
private void dump ( final DataSource dataSource )
{
String sql =
"""
SELECT * FROM bogus_
;
""";
try (
Connection conn = dataSource.getConnection() ;
Statement stmt = conn.createStatement() ;
ResultSet resultSet = stmt.executeQuery( sql ) ;
)
{
System.out.println( "-------------| table start |---------------" );
while ( resultSet.next() )
{
UUID uuid = resultSet.getObject( "id_" , UUID.class );
OffsetDateTime when = resultSet.getObject( "when_" , OffsetDateTime.class );
System.out.println( uuid + " | " + when );
}
System.out.println( "-------------| table end |---------------" );
}
catch ( SQLException e ) { throw new RuntimeException( e ); }
}
}
运行时:
-------------| table start |---------------
97a9e379-4d8f-4d06-8bea-43560a72120b | 2023-02-10T20:32:57.074979Z
052ae129-d0ca-4fdf-9a06-c87d20a2d3f2 | 2023-02-10T20:32:57.080153Z
-------------| table end |---------------
Demo done at 2023-02-10T20:32:57.092230Z
评论
将 JVM 时区更改为 UTC 解决了时间戳的问题,但是,在这种情况下,我无法更新运行此代码的服务器。
无需调整 JVM 时区即可为我工作的解决方案是
statement.setTimestamp(1,
Timestamp.valueOf(DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS")
.format(Instant.now().atZone(ZoneId.of("UTC")))));
这迫使 Timestamp 使用 UTC,尽管它看起来相当健壮和冗余。提出的其他解决方案对我不起作用。
评论
Timestamp
Timestamp
Instant.now()
评论
ps.setObject(1, OffsetDateTime.now(ZoneOffset.UTC))
(或者,如果您很不幸,数据库列没有携带它在 UTC 中的信息)。LocalDateTime.now(ZoneOffset.UTC)
Instant.now
user.timezone
Timestamp
Date
OffsetDateTime
Instant
LocalDateTime
Timestamp