【问题标题】:MySQL Date Changes to Yesterday's date After JPA SaveJPA 保存后 MySQL 日期更改为昨天的日期
【发布时间】:2019-06-22 10:47:06
【问题描述】:

我在使用 Spring 的 JpaRepository 将实体保存到 MySQL 时遇到了日期字段/数据库列的问题。

实体具有 LocalDate 字段。在使用 LocalDate.now() 进行测试时,遇到了返回 Date 字段的问题:

  1. 首先保存返回对象的日期是正确的。
  2. 从 MySQL 数据库返回对象时,日期是前一天

例子:

预计:2019-01-29

实际:2019-01-28

我昨天试过了,结果是:

预计:2019-01-28

实际:2019-01-27

可能类似于这个JPA Saving wrong date in MySQL database

代码

application-mysql-test-connection.properties

spring.jpa.hibernate.ddl-auto=create

# Database url
spring.datasource.url=jdbc:mysql://localhost:3306/test_coupon_system?serverTimezone=UTC

spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver

# Test Database credentials
spring.datasource.username=springuser
spring.datasource.password=springuser

### showing values - for development
spring.jpa.show-sql=true

优惠券为简洁起见删除了构造函数和 getter/setter

@Entity
public class Coupon {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", length = 45)
    private long id;


    @Column(name = "name", unique = true, nullable = false, length = 45)
    private String name;

    @Column(name = "description", length = 100)
    private String description;

    @Column(name = "imageLocation")
    private String imageLocation;

    @Column(name = "startDate", length = 45)
    private LocalDate startDate;

    @Column(name = "endDate", length = 45)
    private LocalDate endDate;

    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "company_id")
    private Company company;

    @ManyToMany(cascade = CascadeType.PERSIST,fetch = FetchType.EAGER)
    @JoinTable(
            name = "customer_coupon",
            joinColumns = @JoinColumn(name = "coupon_id"),
            inverseJoinColumns = @JoinColumn(name = "customer_id")
    )
    private List<Customer> customers;

CouponRepository

@Repository
public interface CouponRepository extends JpaRepository<Coupon, Long> {

    Coupon findByName(String name);
}

测试类带打印输出

@SpringBootTest
@TestPropertySource(locations = {
        "classpath:application-mysql-test-connection.properties",
})
public class CouponDateIT {

    @Autowired
    private CouponRepository repository;

    @BeforeEach
    void setUp() {
        repository.deleteAll();
    }

    @Test
    void returnsLocalDate() {
        LocalDate testDate = LocalDate.now();

        DateTime.now();
        Coupon coupon = new Coupon();
        String couponName = "test1";
        coupon.setName(couponName);
        coupon.setStartDate(LocalDate.now());
        coupon.setEndDate(LocalDate.now());
        coupon = repository.saveAndFlush(coupon);

        System.out.println("===> test date " + testDate);
        System.out.println("===> coupon from db " + coupon);

        assertEquals(testDate, coupon.getStartDate());
        assertEquals(testDate, coupon.getEndDate());

        Coupon returned = repository.findByName(couponName);
        System.out.println("===> after save " + returned);
        assertEquals(testDate, returned.getStartDate());
        assertEquals(testDate, returned.getEndDate());
    }

未通过测试

Hibernate: select coupon0_.id as id1_1_, coupon0_.company_id as company_7_1_, coupon0_.description as descript2_1_, coupon0_.end_date as end_date3_1_, coupon0_.image_location as image_lo4_1_, coupon0_.name as name5_1_, coupon0_.start_date as start_da6_1_ from coupon coupon0_
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: insert into coupon (company_id, description, end_date, image_location, name, start_date, id) values (?, ?, ?, ?, ?, ?, ?)
===> test date 2019-01-29
===> coupon from db Coupon{id=1, name='test1', description='null', imageLocation='null', startDate=2019-01-29, endDate=2019-01-29}
Hibernate: select coupon0_.id as id1_1_, coupon0_.company_id as company_7_1_, coupon0_.description as descript2_1_, coupon0_.end_date as end_date3_1_, coupon0_.image_location as image_lo4_1_, coupon0_.name as name5_1_, coupon0_.start_date as start_da6_1_ from coupon coupon0_ where coupon0_.name=?
Hibernate: select customers0_.coupon_id as coupon_i2_3_0_, customers0_.customer_id as customer1_3_0_, customer1_.id as id1_2_1_, customer1_.email as email2_2_1_, customer1_.name as name3_2_1_ from customer_coupon customers0_ inner join customer customer1_ on customers0_.customer_id=customer1_.id where customers0_.coupon_id=?
===> after save Coupon{id=1, name='test1', description='null', imageLocation='null', startDate=2019-01-28, endDate=2019-01-28}

java.lang.AssertionError: expected:<2019-01-29> but was:<2019-01-28>
Expected :2019-01-29
Actual   :2019-01-28

在测试前记录

2019-01-29 11:26:07.986  INFO 6576 --- [           main] g.f.d.s.database.CouponDateIT            : No active profile set, falling back to default profiles: default
2019-01-29 11:26:09.235  INFO 6576 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2019-01-29 11:26:09.308  INFO 6576 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 73ms. Found 3 repository interfaces.
2019-01-29 11:26:09.762  INFO 6576 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$70fe81c1] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-01-29 11:26:09.981  INFO 6576 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-01-29 11:26:10.894  INFO 6576 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-01-29 11:26:10.957  INFO 6576 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2019-01-29 11:26:11.019  INFO 6576 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.3.7.Final}
2019-01-29 11:26:11.019  INFO 6576 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2019-01-29 11:26:11.191  INFO 6576 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
2019-01-29 11:26:11.341  INFO 6576 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
2019-01-29 11:26:11.591  WARN 6576 --- [           main] org.hibernate.mapping.RootClass          : HHH000038: Composite-id class does not override equals(): .entity.Customer_Coupon
2019-01-29 11:26:11.591  WARN 6576 --- [           main] org.hibernate.mapping.RootClass          : HHH000039: Composite-id class does not override hashCode(): .entity.Customer_Coupon
Hibernate: drop table if exists company
Hibernate: drop table if exists coupon
Hibernate: drop table if exists customer
Hibernate: drop table if exists customer_coupon
Hibernate: drop table if exists hibernate_sequence
Hibernate: create table company (id bigint not null, email varchar(45) not null, name varchar(45) not null, password varchar(45) not null, primary key (id)) engine=MyISAM
Hibernate: alter table company add constraint UK_bma9lv19ba3yjwf12a34xord3 unique (email)
Hibernate: alter table company add constraint UK_niu8sfil2gxywcru9ah3r4ec5 unique (name)
Hibernate: create table coupon (id bigint not null, description varchar(100), end_date date, image_location varchar(255), name varchar(45) not null, start_date date, company_id bigint, primary key (id)) engine=MyISAM
Hibernate: create table customer (id integer not null, email varchar(45) not null, name varchar(45) not null, primary key (id)) engine=MyISAM
Hibernate: create table customer_coupon (customer_id bigint not null, coupon_id bigint not null, primary key (customer_id, coupon_id)) engine=MyISAM
Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: insert into hibernate_sequence values ( 1 )
Hibernate: alter table coupon add constraint UK_dfikvnp7dxdfishfvpnlc0xc1 unique (name)
Hibernate: alter table customer add constraint UK_dwk6cx0afu8bs9o4t536v1j5v unique (email)
Hibernate: alter table customer add constraint UK_crkjmjk1oj8gb6j6t5kt7gcxm unique (name)
Hibernate: alter table coupon add constraint FKe2v6qnb3w90rekqrae28iiqhm foreign key (company_id) references company (id)
Hibernate: alter table customer_coupon add constraint FKppndqdpydmsumc9yqslm5hss4 foreign key (coupon_id) references coupon (id)
Hibernate: alter table customer_coupon add constraint FKi755t5tde9sf6nrp4rm2rnnmn foreign key (customer_id) references customer (id)
2019-01-29 11:26:12.515  INFO 6576 --- [           main] o.h.t.schema.internal.SchemaCreatorImpl  : HHH000476: Executing import script 'ScriptSourceInputFromUrl(/import.sql)'
2019-01-29 11:26:12.515  INFO 6576 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2019-01-29 11:26:12.812  INFO 6576 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
2019-01-29 11:26:13.747  INFO 6576 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-01-29 11:26:13.793  WARN 6576 --- [           main] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2019-01-29 11:26:14.592  INFO 6576 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2019-01-29 11:26:14.655  INFO 6576 --- [           main] g.f.d.s.database.CouponDateIT            : Started CouponDateIT in 6.985 seconds (JVM running for 8.059)

我希望日期字段在保存并返回后不会更改,但日期一直更改为昨天的 AFTER 保存优惠券。

【问题讨论】:

  • 连接字符串中不使用?serverTimezone=UTC会有什么结果?
  • 表格中的列是什么类型的?
  • @Hareesh 该列的类型为日期。
  • @x80486 without ?serverTimezone=UTC throws Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'Jerusalem Standard Time' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
  • 那么数据库中的值是什么?

标签: java mysql hibernate spring-data-jpa


【解决方案1】:

我在 Wildfly 13 中遇到了同样的问题,这是我的数据源:

       <datasource jndi-name="java:/comp/env/isobit" pool-name="isobit-dev" enabled="true" use-java-context="true">
                <connection-url>jdbc:mysql://localhost:3306/isobit?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC</connection-url>
                <driver>mysql-connector-java-8.0.13.jar</driver>

然后把驱动改成mysql-connector-java-5.1.23-bin.jar和我的standalone.xml

             <datasource jndi-name="java:/comp/env/isobit" pool-name="isobit-dev" enabled="true" use-java-context="true">
                <connection-url>jdbc:mysql://localhost:3306/isobit?zeroDateTimeBehavior=convertToNull</connection-url>
                <driver>mysql-connector-java-5.1.23-bin.jar</driver>
                <security>
                    <user-name>root</user-name>
                    <password>root</password>
                </security>
            </datasource>

现在日期是正确的。

【讨论】:

    【解决方案2】:

    我认为是因为时区问题。区域设置日期不考虑您已经知道的时区。但在数据库中,我猜日期与时区相关联。 JPA/Hibernate 层会将 LocaleDate 转换为 TimeStamp(默认在转换过程中将使用 JVM 时区)。您运行应用程序的时区与数据库时区不同,因此存在不匹配。

    要确认这一点,请将运行机器的时区设置为 UTC。

    【讨论】:

      【解决方案3】:

      感谢您的帮助。如上所述并已回答,这是一个时区问题

      解决方案:

      1. 在MySQL中设置SET GLOBAL time_zone = '+02:00';";
      2. spring.datasource.url=jdbc:mysql://localhost:3306/schema_name 中删除?serverTimezone=UTC

      发现这些 MySQL 命令很有用: SELECT @@GLOBAL.time_zone, @@session.time_zone, @@system_time_zone;SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP) as GMT_TIME_DIFF;

      Should MySQL have its timezone set to UTC?MySQL Documentaion 中的其他信息。

      解决方法

      如上所述,在 pom.xml 中更改 Spring Boot 默认 mysql-connector-java

      <dependency>
       <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</>
      </dependency>
      

      properties 文件中spring.datasource.driverClassName=com.mysql.jdbc.Driver 的驱动程序也返回了正确的日期。

      【讨论】:

      • 此解决方案比其他解决方案更好,因为它不针对已经与 LocalDate / LocalDateTime 完美配合的 Java 项目。相反,它旨在配置数据库以设置适当的时区。由于我使用 docker-compose 来启动开发 mysql 服务器,因此我使用 --default-time-zone=America/Sao_Paulo 配置在 mysql 级别解决了这个问题,而不是在 Java 级别。
      【解决方案4】:

      将 Date 字段转换为 TimeStamp 将解决问题并确保您通过 spring 属性文件传递时区

      spring.jpa.properties.hibernate.jdbc.time_zone=GMT+10

      【讨论】:

        【解决方案5】:

        在我的国家,时间是 GMT+1,我遇到了同样的问题。

        我首先做的是serverTimezone=GMT+1,这给了我错误。

        但是JDBC使用了所谓的“连接URL”,所以可以用“%2B”转义“+”,即

        db.url=jdbc:mysql://localhost:3306/MyDB?useSSL=false&serverTimezone=GMT%2B1&useLegacyDatetimeCode=false
        

        P.S:查看此Why useLegacyDatetimeCode=false 以了解更多信息。

        希望它可以帮助某人。如果它确实不要忘记投票。

        【讨论】:

        • 工作就像一个魅力谢谢哥们!!!!
        • @ZINEMahmoud 欢迎您:D
        【解决方案6】:

        例如,如果您的时区是

        Europe/Warsaw
        

        你可以设置:

        spring.datasource.url=jdbc:mysql://localhost:3306/database?serverTimezone=Europe/Warsaw
        

        而不是这个:

        spring.datasource.url=jdbc:mysql://localhost:3306/database?serverTimezone=UTC
        

        但您也可以在 application.properties 中再添加一项:

        spring.jpa.properties.hibernate.jdbc.time_zone=Europe/Warsaw
        

        【讨论】:

        • 我已经设置了与时区相关的其他属性,除了 spring.jpa.properties.hibernate.jdbc.time_zone 并且通过添加这个属性现在日期被保存在正确的时区
        • 为数据源设置 spring.datasource.url=jdbc:mysql://localhost:3306/database?serverTimezone=Europe/Warsaw 对我有用并且是完美的解决方案,因为我有多个数据源,可以有不同的时区。感谢您的解决方案
        【解决方案7】:

        这个问题在 Spring boot 2.2.7 中再次出现 - 根本原因似乎是 mysql 连接器版本 8.0.20 中的错误。

        我向甲骨文报告了这个:https://bugs.mysql.com/bug.php?id=99487

        目前,我将mysql连接器降级到8.0.19,它可以正常工作:

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
                <version>8.0.19</version>
            </dependency>
        

        【讨论】:

        • 已解决:spring.datasource.hikari.data-source-properties.cacheDefaultTimezone=false
        • 我会在您的实际答案中包含cacheDefaultTimezone 解决方案。这可能是比降级库更好的解决方法,因此将其包含在答案中将是有益的。
        【解决方案8】:

        要解决这个问题,我们只需将服务器时区设置为 UTC,因为每个地方的时区都不同。

        在 application.properties 文件中添加或更新以下行:

        spring.datasource.url=jdbc:mysql://localhost:3306/test_coupon_system?serverTimezone=UTC

        【讨论】:

          【解决方案9】:

          时区不匹配在您的本地时区运行的 Java 代码与在 UTC 时区的 MySQL 之间。

          根据您所在的时区,这一天的差异仅在您的一天与 UTC 不同的特定时间发生。

          将 MySQL 中的连接字符串更改为与您的计算机相同的时区。

          【讨论】:

            猜你喜欢
            • 2013-03-15
            • 2023-03-08
            • 1970-01-01
            • 2018-11-04
            • 2021-11-09
            • 1970-01-01
            • 2015-04-23
            • 1970-01-01
            相关资源
            最近更新 更多