【发布时间】:2020-06-02 07:15:06
【问题描述】:
我正在用 Java8 + MyBatis + MySQL5.7 来庆祝生日。我的代码是这样的:
// 'birthday' is a DATE column
@Insert("insert into MyTable (id, birthday) values (#{id"}, #{date})")
void saveDate(int id, LocalDate date);
@Select("select birthday from MyTable where id=#{id"}")
LocalDate loadDate(@Param("id") int id);
以及保存和加载日期的代码。
final int id = 1;
final LocalDate date = LocalDate.of(2020, 1, 3);
repo.saveDate(id, date);
LocalDate dateFromDB = repo.loadDate(id);
System.out.println(date); // 2020-1-3
System.out.println(dateFromDB); // 2020-1-2
我注意到“dateFromDB”的值总是比“日期”早一天。我认为这是由时区转换引起的。由于我的应用程序是 UTC+8,而 DB 使用的是 UTC。通过使用 DATETIME 而不是 DATE,我可以看到日期“2020-1-3”保存为“2020-1-2 16:0:0”。
所以问题是在保存生日等日期时如何避免时区转换?
----------------- 更新于 2020-02-20 ------------------------------------ ------------
到目前为止,我找到了 4 种方法:
- 添加参数
noTzConversionForDateType=true以避免仅转换日期类型。这是最好的方法,但仅适用于 5.1 版。我真的不明白为什么它会从 8.0 版中删除... (document) - 添加参数
serverTimezone=GMT%2B8使用客户端时区(UTC+8)作为服务器时区。转换仍然发生,但没有任何改变。问题是TIMESTAMP列也会受到影响。 - 向JVM添加参数
-Duser.timezone=UTC。问题与 #2 类似。 - 将
LocalDate值发送为String。更新saveDate函数如下(只需将类型从LocalDate更改为String):
@Insert("insert into MyTable (id, birthday) values (#{id"}, #{date})")
void saveDate(int id, String date);
然后在调用saveDate之前将日期转换为String:
String dateString = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
repo.saveDate(id, dateString);
注意,DB 中的数据类型仍然是DATE。这种方式的好处是只有birthday 列会受到影响。问题是代码不够优雅,可能会导致大型数据集的性能问题。
【问题讨论】:
-
您要么必须调整数据库的时区配置,要么在存储/加载数据之前/之后将日期从/转换到所需的时区。也许,
TIMESTAMP也可以,可以与Instant一起使用。我认为你应该转换,也许在 Java 中使用ZonedDateTime或OffsetDateTime并让数据库以 UTC 存储所有内容。 -
@KenZhang 这是 Connector/J 8.0.x 的一个已知问题。见bugs.mysql.com/bug.php?id=93444。将
serverTimezone添加到 JDBC URL 是目前的解决方法。在你的情况下,它看起来像jdbc:mysql://yourhost:3306/dbname?serverTimezone=GMT%2B8。您应该点击工单上的“影响我”。
标签: java mysql timezone mybatis localdate