休眠时存储的 LocalDate 错误
Wrong LocalDate stored with hibernate
我试图在数据库中存储餐厅预订的日期,但是,即使我提交的日期是正确的,hibernate 在数据库中存储的日期比我提交的日期早一天。我不知道为什么...这可能是时区问题,但我不明白为什么...日期不应受时区影响。
这是我的 spring 引导属性文件:
spring:
thymeleaf:
mode: HTML5
encoding: UTF-8
cache: false
jpa:
database: MYSQL
hibernate:
ddl-auto: update
properties:
hibernate:
locationId:
new_generator_mappings: false
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
jdbc:
time_zone: UTC
datasource:
driver:
class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: username
password: **********
我来自意大利,所以我的时区是这样的:
- GMT/UTC + 标准时间 1 小时
- GMT/UTC + 夏令时 2 小时
目前我们是 UTC + 2h。
我存储的对象是这个:
@Entity
public class Dinner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long dinnerId;
private LocalDate date;
...
我用来拦截 POST 请求的控制器是这样的:
@PreAuthorize("hasRole('USER')")
@PostMapping
public String createDinner(@RequestParam(value="dinnerDate") String dinnerDate, Principal principal, Model model){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(dinnerDate, formatter);
dinnerService.createDinner(date);
return "redirect:/dinners?dinnerDate=" + dinnerDate;
}
调用服务方法createDinner调用Jpa方法save来存储对象。
我正在使用 thymeleaf 来处理 html 模板。
如果我在数据库中提交日期 30/6/2019,我会得到 29/6/2019。当我按日期检索晚餐对象时,如果我插入 30/6/2019,我将获得日期为 29/6/2019 的晚餐。所以似乎 spring 以一种奇怪的方式自行处理日期......考虑到某种时区,但我不知道如何禁用或处理它。有什么想法吗?
假设您的时区是:Europe/Italy,您必须像这样设置您的 serverTimezone 变量:
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Italy
同样的问题(和同一个国家!:-))。
我怀疑如果 hibernate 或 jpa 设置了时区 UTC
,而机器设置了默认时区 == Europe/Rome
当一个日期被持久化时,那么它将自动从机器时区到数据库时区,如果您将所有日期都以 UTC 格式存储在数据库上,这不是一个坏功能。
在持久化之前转换日期时会出现问题:它被转换了两次。至少,我是这样。
仍在寻找最佳解决方案!万一我找到了,我会稍后将其添加到答案中。
您不需要为模式定义格式 yyyy-MM-dd
。 LocalDate#parse 默认使用 DateTimeFormatter.ISO_LOCAL_DATE
,这意味着 LocalDate.parse("2020-06-29")
无需显式应用格式即可工作。
既然您已经知道您所在时区的日期时间与 UTC 不同,您永远不应该只考虑日期;相反,您应该同时考虑日期和时间,例如11:30 UTC 时间 2020-06-29 下午将落在您所在时区的 2020-06-30。因此,您首先要做的是在数据库中将字段的类型更改为TIMESTAMP
。
将字段类型更改为 TIMESTAMP
后,更改方法 createDinner
如下:
LocalDateTime dinnerDateTime = LocalDateTime.of(LocalDate.parse(dinnerDate), LocalTime.of(0, 0, 0, 0));
OffsetDateTime odt = dinnerDateTime.atOffset(ZoneOffset.UTC);
dinnerService.createDinner(odt);
然后在 DinnerService
(或 DinnerServiceDAO
任何你将逻辑写入数据库中 insert/update 记录的地方):
pst.setObject(index, odt);
其中 pst
表示 PreparedStatement
的对象,index
表示您的 insert/update 查询中此字段的索引(以 1
开头)。
我试图在数据库中存储餐厅预订的日期,但是,即使我提交的日期是正确的,hibernate 在数据库中存储的日期比我提交的日期早一天。我不知道为什么...这可能是时区问题,但我不明白为什么...日期不应受时区影响。
这是我的 spring 引导属性文件:
spring:
thymeleaf:
mode: HTML5
encoding: UTF-8
cache: false
jpa:
database: MYSQL
hibernate:
ddl-auto: update
properties:
hibernate:
locationId:
new_generator_mappings: false
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
jdbc:
time_zone: UTC
datasource:
driver:
class: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: username
password: **********
我来自意大利,所以我的时区是这样的:
- GMT/UTC + 标准时间 1 小时
- GMT/UTC + 夏令时 2 小时
目前我们是 UTC + 2h。
我存储的对象是这个:
@Entity
public class Dinner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long dinnerId;
private LocalDate date;
...
我用来拦截 POST 请求的控制器是这样的:
@PreAuthorize("hasRole('USER')")
@PostMapping
public String createDinner(@RequestParam(value="dinnerDate") String dinnerDate, Principal principal, Model model){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(dinnerDate, formatter);
dinnerService.createDinner(date);
return "redirect:/dinners?dinnerDate=" + dinnerDate;
}
调用服务方法createDinner调用Jpa方法save来存储对象。 我正在使用 thymeleaf 来处理 html 模板。 如果我在数据库中提交日期 30/6/2019,我会得到 29/6/2019。当我按日期检索晚餐对象时,如果我插入 30/6/2019,我将获得日期为 29/6/2019 的晚餐。所以似乎 spring 以一种奇怪的方式自行处理日期......考虑到某种时区,但我不知道如何禁用或处理它。有什么想法吗?
假设您的时区是:Europe/Italy,您必须像这样设置您的 serverTimezone 变量:
url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Italy
同样的问题(和同一个国家!:-))。
我怀疑如果 hibernate 或 jpa 设置了时区 UTC
,而机器设置了默认时区 == Europe/Rome
当一个日期被持久化时,那么它将自动从机器时区到数据库时区,如果您将所有日期都以 UTC 格式存储在数据库上,这不是一个坏功能。
在持久化之前转换日期时会出现问题:它被转换了两次。至少,我是这样。
仍在寻找最佳解决方案!万一我找到了,我会稍后将其添加到答案中。
您不需要为模式定义格式
yyyy-MM-dd
。 LocalDate#parse 默认使用DateTimeFormatter.ISO_LOCAL_DATE
,这意味着LocalDate.parse("2020-06-29")
无需显式应用格式即可工作。既然您已经知道您所在时区的日期时间与 UTC 不同,您永远不应该只考虑日期;相反,您应该同时考虑日期和时间,例如11:30 UTC 时间 2020-06-29 下午将落在您所在时区的 2020-06-30。因此,您首先要做的是在数据库中将字段的类型更改为
TIMESTAMP
。将字段类型更改为
TIMESTAMP
后,更改方法createDinner
如下:LocalDateTime dinnerDateTime = LocalDateTime.of(LocalDate.parse(dinnerDate), LocalTime.of(0, 0, 0, 0)); OffsetDateTime odt = dinnerDateTime.atOffset(ZoneOffset.UTC); dinnerService.createDinner(odt);
然后在
DinnerService
(或DinnerServiceDAO
任何你将逻辑写入数据库中 insert/update 记录的地方):pst.setObject(index, odt);
其中
pst
表示PreparedStatement
的对象,index
表示您的 insert/update 查询中此字段的索引(以1
开头)。