【问题标题】:Wrong LocalDate stored with hibernate使用休眠存储的 LocalDate 错误
【发布时间】:2019-11-05 06:49:16
【问题描述】:

我试图将餐厅预订的日期存储在数据库中,但是即使我提交的日期是正确的,hibernate 也会在我提交的日期前一天将日期存储在数据库中。我不知道为什么...可能是时区问题,但我不明白为什么...日期不应该受时区影响。

这是我的 Spring Boot 属性文件:

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 的晚餐。所以似乎春天以一种奇怪的方式自己处理日期......考虑到某种时区,但我不知道如何禁用或处理它。有什么想法吗?

【问题讨论】:

  • date的列数据库类型是什么?另外请添加createDinner(LocalDate date) 方法的代码。 + 看这里:stackoverflow.com/questions/43476364/… : "...所以如果您想要上午 9 点的约会,您应该使用记录在 TIMESTAMP WITHOUT TIME ZONE 类型的数据库列中的 LocalTime 或 LocalDateTime..."
  • 类型是DATE,正如我所说的createDinner 只是调用save。
  • 还有一些问题:保存实体后,您是否检查过数据库中的实际内容?以及如何检索 Dinner 对象?
  • 正如我所说:“如果我在数据库中提交 2019 年 6 月 30 日的日期,我会得到 2019 年 6 月 29 日”所以是的,我检查了......这就是重点!我通过调用 JpaRepository 方法 findById 来检索晚餐对象。但问题出在数据库中......它保存了错误的一天......
  • 您使用的是什么 MySQL 版本和连接器?您是否也在使用第三方连接池?这可能是相关的:bugs.mysql.com/bug.php?id=71084

标签: spring hibernate jpa thymeleaf localdate


【解决方案1】:
  1. 您不需要为模式yyyy-MM-dd 定义格式。 LocalDate#parse 默认使用 DateTimeFormatter.ISO_LOCAL_DATE,这意味着 LocalDate.parse("2020-06-29") 无需显式应用格式即可工作。

  2. 由于您已经知道您所在时区中的日期时间与 UTC 中的不同,因此您永远不应该只考虑日期;相反,您应该同时考虑日期和时间,例如UTC 时间 2020 年 6 月 29 日晚上 11:30 将落在您所在时区的 2020 年 6 月 30 日。因此,您应该首先将数据库中字段的类型更改为TIMESTAMP

  3. 将字段类型更改为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,只要您编写了在数据库中插入/更新记录的逻辑):

    pst.setObject(index, odt);
    

    其中pst 表示PreparedStatement 的对象,index 表示插入/更新查询中该字段的索引(以1 开头)。

【讨论】:

    【解决方案2】:

    同样的问题(同样的国家!:-))。

    我怀疑如果hibernate或jpa设置了时区UTC,而机器设置了默认时区==Europe/Rome,当一个日期被持久化时,它会自动从机器时区转换为数据库时区,如果您将所有日期都以 UTC 格式存储在数据库中,这不是一个坏功能。

    当您在持久化之前转换日期时会出现问题:它被转换了两次。至少,这是我的情况。

    仍在寻找最佳解决方案!万一我找到了,我会在稍后将其添加到答案中。

    【讨论】:

    • 您找到解决方案了吗?我认为这似乎是一个错误:bugs.mysql.com/bug.php?id=104822&thanks=4
    • 不.. 我不再从事那个项目了。但我使用的是 PostgreSql,链接在 mysql 论坛上......它可能相关,但我不是 Hibernate 的专家!
    【解决方案3】:

    假设您的时区是:Europe/Italy,您必须像这样设置 serverTimezone 变量:

    网址:jdbc:mysql://localhost:3306/databaseName?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Europe/Italy

    【讨论】:

      猜你喜欢
      • 2017-03-23
      • 2015-01-05
      • 2021-06-11
      • 1970-01-01
      • 2018-06-18
      • 2011-05-28
      • 2012-05-14
      相关资源
      最近更新 更多