为什么 Java 和数据库时区会自动更改?
Why is the Java and DB time-zone automatically changed?
我在使用 java、spring、mysql 时发现了一个未知问题。
mysql 时区 = UTC。
和 tomcat 时区 = KST。喜欢这个代码
@PostConstruct
void postConstruct() {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
}
现在,让我们检查一下 Java 时区。
TimeZone aDefault = TimeZone.getDefault();
System.out.println("aDefault = " + aDefault); // 'Asia/Seoul' It is what i wanted.
LocalDateTime = LocalDateTime.now();
System.out.println("localDateTime = " + localDateTime); // It is based on KST. what i wanted
问题是我 运行 在数据库上查询的那一刻。
//使用了jpa,语义解释省略语法
// jpa
@Query("select a from A a where datetime >= : dateTime")
明确确认查询是在KST时间执行的,DB是以UTC时间保存的,但是DB中的值是根据KST导入符合条件的。
我真的不知道如何自动计算数据库时间。
例如,应用程序将数据存储在10点钟,值存储在DB中的1点钟(-9)。
我立即 运行 查询,所以我检查它是否发送到 select ~ where datetime >= 10:00:00
。
那么,由于是存储在DB中的1点,所以没有导入数据是正常的,但是数据是正常导入的。
我不使用 Spring Boot 或 JPA。但我可以给你一些一般性的观点。
您的问题不是很清楚,但您似乎正在尝试使用类似于 SQL 类型 TIMESTAMP WITH TIME ZONE
的数据类型的列在 table 中查找行。对于 JDBC 4.2,您应该使用 OffsetDateTime
class 因为它是与 SQL 标准类型 TIMESTAMP WITH TIME ZONE
.
匹配的类型
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
在直接 JDBC 代码中,我将使用带有 ?
占位符和 SQL 的准备好的语句,如下所示:
SELECT *
FROM some_table
WHERE some_column >= ?
;
在准备好的语句中,传递 OffsetDateTime
作为 ?
的值。
myPreparedStatement.setObject( … , odt ) ;
检索。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
您可以将 OffsetDateTime
调整为特定时区。同一时刻,时间轴上的同一点,不同的挂钟时间。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
关于您的示例代码……我想不出在任何情况下调用 LocalDateTime.now()
是正确的做法,因为该类型不能代表一个时刻,不是时间轴上的一个点。相反,使用 Instant.now()
表示 UTC 中的当前时刻,或 ZonedDateTime.now()
表示 JVM 当前默认时区中的当前时刻。对于 JDBC 工作,如上所示使用 OffsetDateTime
。
你说:
For example, the application stores data at 10 o'clock and the value is stored at 1 o'clock(-9) in the DB.
或者:
- 您在列中使用了不正确的数据类型。要记录时刻,时间线上的特定点,您必须使用
TIMESTAMP WITH TIME ZONE
,而不是 WITHOUT
。
- 在 MySQL 中,这意味着您需要使用
TIMESTAMP
,而不是 DATETIME
。
- 您正在使用自以为是的中间件或工具,其反功能是在从数据库检索时注入时区调整。
永远不要使用 TimeZone
class。 class 是多年前被现代 java.time classes 取代的遗留日期时间 classes 的一部分。具体替换为 ZoneId
& ZoneOffset
.
最后,我只是勉强提一下这个警告:如果你要预约未来的约会或类似的事情,这是错误的方法。如果您希望即使政客更改您所在时区的规则,一天中的时间也保持固定的约会(因此无论夏令时或其他时钟操作如何变化,下午 3 点的牙科预约都会保持在下午 3 点),那么您应该使用两个列,一个类似于SQL-标准类型TIMESTAMP WITHOUT TIME ZONE
。并使用包含预期时区名称的文本类型的第二列。当需要在需要特定时刻的地方动态创建日历时,请在运行时组合这两个值以生成 ZonedDateTime
对象。那一刻只能在运行时短暂使用,永远不会存储。这个话题已经在 Stack Overflow 上被多次提及。搜索以了解更多信息。
我在使用 java、spring、mysql 时发现了一个未知问题。
mysql 时区 = UTC。
和 tomcat 时区 = KST。喜欢这个代码
@PostConstruct
void postConstruct() {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
}
现在,让我们检查一下 Java 时区。
TimeZone aDefault = TimeZone.getDefault();
System.out.println("aDefault = " + aDefault); // 'Asia/Seoul' It is what i wanted.
LocalDateTime = LocalDateTime.now();
System.out.println("localDateTime = " + localDateTime); // It is based on KST. what i wanted
问题是我 运行 在数据库上查询的那一刻。
//使用了jpa,语义解释省略语法
// jpa
@Query("select a from A a where datetime >= : dateTime")
明确确认查询是在KST时间执行的,DB是以UTC时间保存的,但是DB中的值是根据KST导入符合条件的。
我真的不知道如何自动计算数据库时间。
例如,应用程序将数据存储在10点钟,值存储在DB中的1点钟(-9)。
我立即 运行 查询,所以我检查它是否发送到 select ~ where datetime >= 10:00:00
。
那么,由于是存储在DB中的1点,所以没有导入数据是正常的,但是数据是正常导入的。
我不使用 Spring Boot 或 JPA。但我可以给你一些一般性的观点。
您的问题不是很清楚,但您似乎正在尝试使用类似于 SQL 类型 TIMESTAMP WITH TIME ZONE
的数据类型的列在 table 中查找行。对于 JDBC 4.2,您应该使用 OffsetDateTime
class 因为它是与 SQL 标准类型 TIMESTAMP WITH TIME ZONE
.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
在直接 JDBC 代码中,我将使用带有 ?
占位符和 SQL 的准备好的语句,如下所示:
SELECT *
FROM some_table
WHERE some_column >= ?
;
在准备好的语句中,传递 OffsetDateTime
作为 ?
的值。
myPreparedStatement.setObject( … , odt ) ;
检索。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
您可以将 OffsetDateTime
调整为特定时区。同一时刻,时间轴上的同一点,不同的挂钟时间。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;
关于您的示例代码……我想不出在任何情况下调用 LocalDateTime.now()
是正确的做法,因为该类型不能代表一个时刻,不是时间轴上的一个点。相反,使用 Instant.now()
表示 UTC 中的当前时刻,或 ZonedDateTime.now()
表示 JVM 当前默认时区中的当前时刻。对于 JDBC 工作,如上所示使用 OffsetDateTime
。
你说:
For example, the application stores data at 10 o'clock and the value is stored at 1 o'clock(-9) in the DB.
或者:
- 您在列中使用了不正确的数据类型。要记录时刻,时间线上的特定点,您必须使用
TIMESTAMP WITH TIME ZONE
,而不是WITHOUT
。- 在 MySQL 中,这意味着您需要使用
TIMESTAMP
,而不是DATETIME
。
- 在 MySQL 中,这意味着您需要使用
- 您正在使用自以为是的中间件或工具,其反功能是在从数据库检索时注入时区调整。
永远不要使用 TimeZone
class。 class 是多年前被现代 java.time classes 取代的遗留日期时间 classes 的一部分。具体替换为 ZoneId
& ZoneOffset
.
最后,我只是勉强提一下这个警告:如果你要预约未来的约会或类似的事情,这是错误的方法。如果您希望即使政客更改您所在时区的规则,一天中的时间也保持固定的约会(因此无论夏令时或其他时钟操作如何变化,下午 3 点的牙科预约都会保持在下午 3 点),那么您应该使用两个列,一个类似于SQL-标准类型TIMESTAMP WITHOUT TIME ZONE
。并使用包含预期时区名称的文本类型的第二列。当需要在需要特定时刻的地方动态创建日历时,请在运行时组合这两个值以生成 ZonedDateTime
对象。那一刻只能在运行时短暂使用,永远不会存储。这个话题已经在 Stack Overflow 上被多次提及。搜索以了解更多信息。