简体   繁体   English

在 AWS 上从 MySQL 5.5 升级到 5.6 时 Hibernate 和日期的问题

[英]Problems with Hibernate and dates when upgrading from MySQL 5.5 to 5.6 on AWS

I have a very old legacy Java 6 app with hibernate3 and it connects to an AWS RDS MySQL 5.5 database.我有一个非常古老的 Java 6 应用程序,带有 hibernate3,它连接到 AWS RDS MySQL 5.5 数据库。

It was working everything ok, but AWS now has an end of life for MySQL 5.5 and I'm upgrading to 5.6.一切正常,但 AWS 现在 MySQL 5.5 的生命周期结束,我正在升级到 5.6。 As soon as I upgraded to 5.6 I started having this error:升级到 5.6 后,我开始出现此错误:

ERROR org.hibernate.util.JDBCExceptionReporter - Data truncation: Incorrect datetime value: '' for column 'date' at row 1
org.hibernate.exception.DataException: could not insert: [thebusiness.core.history.History]

For the hibernate model I have:对于 hibernate model 我有:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
    
<hibernate-mapping>
<class name="thebusiness.core.history.History" table="history">
    <id name="id" type="int" column="id">
        <generator class="native" />
    </id>
    <property name="objectType" type="int" column="object_type" not-null="true" length="3"/>
    <property name="recordId" type="int" column="record_id" not-null="true" length="7"/>
    <property name="initialValue" type="java.lang.String" column="initial_value" not-null="true" />
    <property name="setValue" type="java.lang.String" column="set_value" not-null="true" />
    <property name="initialValueId" type="int" column="initial_value_id" not-null="true" />
    <property name="setValueId" type="int" column="set_value_id" not-null="true" />
    <property name="date" type="java.util.Date" column="date" not-null="true"/>
    <property name="fieldName" type="java.lang.String" column="field_name" length="255"/>
    <property name="fieldNameArg0" type="java.lang.String" column="field_name_arg0" length="255"/>
    <property name="comment" type="java.lang.String" column="comment" />    
    <property name="finalComment" type="java.lang.String" column="final_comment" />    
    <many-to-one name="contact" class="thebusiness.core.contact.Contact" not-null="false" lazy="false">
        <column name="contact_id"/>
    </many-to-one>
</class>
</hibernate-mapping>

I debug what date value was being sent but I have a java date value and not an empty value.我调试发送的日期值,但我有一个 java 日期值而不是空值。

Is there anything from 5.5 to 5.6 that could make the app crash here?从 5.5 到 5.6 有什么东西可以让应用程序在这里崩溃吗?

SHOW CREATE TABLE for history table为历史表显示创建表

CREATE TABLE `history` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `object_type` tinyint(3) NOT NULL DEFAULT '0',
  `record_id` int(11) NOT NULL DEFAULT '0',
  `initial_value` varchar(255) NOT NULL,
  `initial_value_id` int(11) NOT NULL DEFAULT '0',
  `set_value` varchar(255) NOT NULL,
  `set_value_id` int(11) NOT NULL DEFAULT '0',
  `field_name` char(255) DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `contact_id` int(11) NOT NULL DEFAULT '0',
  `comment` text,
  `field_name_arg0` char(255) DEFAULT NULL,
  `final_comment` text,
  PRIMARY KEY (`id`),
  KEY `IX_history_record_id` (`record_id`,`field_name`,`object_type`),
  KEY `IX_date` (`date`),
  KEY `IX_object_type` (`object_type`)
) ENGINE=MyISAM AUTO_INCREMENT=82991415 DEFAULT CHARSET=utf8

Update 23th March 2021 2021 年 3 月 23 日更新

I was able to make it work partially by changing the date property type on the table hibernate configuration file from java.util.Date to java.sql.Date but this only saves the Date and before I was also saving the time: I was able to make it work partially by changing the date property type on the table hibernate configuration file from java.util.Date to java.sql.Date but this only saves the Date and before I was also saving the time:

<property name="date" type="java.sql.Date" column="date" not-null="true"/>

Update 25th March 2021 2021 年 3 月 25 日更新

Versions:版本:

  • MySQL: 5.6.49 MySQL:5.6.49
  • MySQL Connector: 5.1.49 MySQL 连接器:5.1.49
  • Hibernate: 3.1.3 Hibernate:3.1.3

Update 26th March 2021 2021 年 3 月 26 日更新

How Date is populated:如何填充日期:

On the class that represents the object I have something like:在代表 object 的 class 上,我有类似的东西:

public class History extends ModelBase implements java.io.Serializable {
  // ...
  private Date date;
  // ...

  public Date getDate() {
    return this.date;
  }

  public void setDate(Date date) {
    this.date = date;
  }

  // ...
}

And to set the date, I'm just using:并设置日期,我只是使用:

history.setDate(new Date());

Newer versions of mysql enable the STRICT_TRANS_TABLES mode, which essentially gives errors whenever what you try to insert is not what will end up stored in the table.较新版本的 mysql 启用 STRICT_TRANS_TABLES 模式,当您尝试插入的内容不是最终存储在表中的内容时,该模式本质上会出错。 Here, you have a date field that you are not setting, so it tries to insert '', which when cast to a date becomes 0000-00-00.在这里,您有一个未设置的日期字段,因此它尝试插入“”,当转换为日期时变为 0000-00-00。

You should set sql_mode appropriately;您应该适当地设置 sql_mode; do select @@sql_mode and see which of the new values you might want to keep, then set it in your aws parameter group.执行select @@sql_mode并查看您可能想要保留哪些新值,然后在您的 aws 参数组中设置它。

If this date field is truly optional, I would encourage you to allow it to be NULL and use NULL as the not-present value, not 0000-00-00.如果此日期字段确实是可选的,我鼓励您允许它为 NULL 并使用 NULL 作为不存在的值,而不是 0000-00-00。 I would also encourage you to upgrade to a current version (mysql 8 or mariadb 10.5);我还鼓励您升级到当前版本(mysql 8 或 mariadb 10.5); while this is likely to cause more upgrade issues now, it will save you from having to upgrade again soon, as well as providing you with a great number of new features.虽然现在这可能会导致更多升级问题,但它将使您不必很快再次升级,并为您提供大量新功能。

You probably want java.sql.Timestamp , which gives you date and time.您可能想要java.sql.Timestamp ,它为您提供日期时间。 Be aware that there may be issues with timezones.请注意,时区可能存在问题。

Meanwhile, please move away from MyISAM to InnoDB.同时,请远离 MyISAM 到 InnoDB。 (There are many reasons for this; it has been discussed repeatedly over the past decade.) (造成这种情况的原因有很多;在过去的十年中已经反复讨论过。)

What is the logic in having set_value and set_value_id in the same table that is not a table with just those two columns ?在同一个表中设置set_valueset_value_id而不是只有这两列的表的逻辑是什么?

Similar problems to the one you indicated has been already reported in Stackoverflow. Stackoverflow 中已经报告了与您指出的问题类似的问题。

The problems seems related with the different treatment provided when executing your DML sentences through Hibernate to the new version of MySQL.这些问题似乎与通过 Hibernate 到新版本的 MySQL 执行 DML 语句时提供的不同处理有关。

In order to solve the problem, first, as suggested in the comments, please, consider use one of a recent mysql connector/j 5.1 version , it seems to be supported in Java 1.6 and is suitable for mysql 5.6 .为了解决这个问题,首先,按照评论中的建议,请考虑使用最近的mysql 连接器/j 5.1 版本之一,它似乎在 Java 1.6 中受支持,适用于 Z81C3B050DAD78.5372FE .

You are using a quite dated Hibernate version: please, consider to upgrade your Hibernate dependencies to a more recent version as well.您使用的是相当过时的 Hibernate 版本:请考虑将您的 Hibernate 依赖项也升级到更新的版本。 I think that both the 3.x and 4.x branches can be appropriate for your problem.我认为3.x4.x分支都适合您的问题。

As you indicated, using java.sql.Date instead of java.util.Date seems to solve the data truncation problem, but as a result you are loosing the time information.正如您所指出的,使用java.sql.Date而不是java.util.Date似乎可以解决数据截断问题,但结果是您正在浪费时间。

As pointed out in the different comments to your question, link for possible duplicate , and later answer of @RickJames, when you use java.sql.Date you are loosing the time information.正如在对您的问题的不同评论中指出的那样,当您使用java.sql.Date时,可能会出现重复的链接以及@RickJames 的答案,您正在失去时间信息。 In order to preserve it, try to use java.sql.Timestamp instead.为了保存它,请尝试使用java.sql.Timestamp代替。

For your comments, you have a lot of tables and the change will involve to convert all dates to timestamp before saving .对于您的评论,您有很多表格,更改将涉及在保存之前将所有日期转换为时间戳

In order to avoid such a level of conversions, one thing you can try is to change your properties definition to use timestamp instead of date when the underlying column is of type DATETIME like in your example.为了避免这种级别的转换,您可以尝试的一件事是在基础列的类型为DATETIME时更改属性定义以使用timestamp而不是date ,就像您的示例中一样。 For instance:例如:

<property name="date" type="timestamp" column="date" not-null="true"/>

java.util.Date already contains the necessary time information, and Hibernate will by default map timestamp types to DATETIME when using MySQL. java.util.Date已经包含了必要的时间信息,而 Hibernate 将默认 map timestamp类型为DATETIME时。

Update更新

For your comments, it seems the actual problem is that if you use Timestamp in any way, no matter how, you face the truncation error.对于您的评论,实际问题似乎是,如果您以任何方式使用Timestamp ,无论如何,您都会面临截断错误。

One thing you can try is to perform a quick test: please, create a new table from scratch with DATETIME fields, and see if the problem continues when you insert data in the new table, maybe the problem could be related with the migration itself.您可以尝试的一件事是执行快速测试:请从头开始使用DATETIME字段创建一个新表,并查看在新表中插入数据时问题是否仍然存在,也许问题可能与迁移本身有关。 I do not know how you actually migrated the database but as you can see in the upgrade guide there are important changes between how the actual information of time related types is stored in mysql 5.6.我不知道您实际上是如何迁移数据库的,但正如您在升级指南中看到的那样,时间相关类型的实际信息在 mysql 5.6 中的存储方式之间存在重要变化。

To verify this last point, you can try to configure your application to run against a local database and perform some test, and see if the problem is still there.要验证最后一点,您可以尝试将应用程序配置为针对本地数据库运行并执行一些测试,看看问题是否仍然存在。 As I told you in my comments, I tried your setup, with the same library versions and the same MySQL database version, I created a table very similar to the one you provided, and just perform a quick test, and no matter if I use java.util.Date or type="timestamp" , it is always working properly.正如我在评论中告诉你的那样,我尝试了你的设置,使用相同的库版本和相同的 MySQL 数据库版本,我创建了一个与你提供的表非常相似的表,然后执行快速测试,无论我是否使用java.util.Datetype="timestamp" ,它总是工作正常。 The only difference with your setup is the JDK version, I am unable to use JDK 1.6 and I tested the code with version 1.8.与您的设置的唯一区别是 JDK 版本,我无法使用 JDK 1.6,我用 1.8 版测试了代码。

My code is the following.我的代码如下。

hibernate.cfg.xml hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.username">test</property>
        <property name="hibernate.connection.password">test</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test?nullNamePatternMatchesAll=true</property>
        <property name="show_sql">true</property>
        <property name="format_sql">false</property>
        <mapping resource="history.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Simplified History class简化History class

package thebusiness.core.history;

import java.util.Date;

public class History {

  private Integer id;
  private String fieldName;
  private Date date;

  public History() {
  }

  public History(Integer id, String fieldName, Date date) {
    this.id = id;
    this.fieldName = fieldName;
    this.date = date;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getFieldName() {
    return fieldName;
  }

  public void setFieldName(String fieldName) {
    this.fieldName = fieldName;
  }

  public Date getDate() {
    return date;
  }

  public void setDate(Date date) {
    this.date = date;
  }
}

history.hbm.xml历史.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
    <class name="thebusiness.core.history.History" table="history2">
        <id name="id" type="int" column="id">
            <generator class="native" />
        </id>
        <property name="date" type="java.util.Date" column="date" not-null="true"/>
        <property name="fieldName" type="java.lang.String" column="field_name" length="255"/>
    </class>
</hibernate-mapping>

Test测试

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class Main {

  public static void main(String[] args) throws ParseException {

    SessionFactory factory;

    try {
      factory = new Configuration().configure().buildSessionFactory();
    } catch (Throwable ex) {
      System.err.println("Failed to create sessionFactory object." + ex);
      throw new ExceptionInInitializerError(ex);
    }

    Session session = factory.openSession();
    Transaction tx = null;
    Integer id = null;

    //SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //Date date = format.parse("2021-03-26 18:34:23");
    // Following your example
    Date date = new Date();

    try {
      tx = session.beginTransaction();
      History history = new History(1, "name", date);
      id = (Integer) session.save(history);
      tx.commit();
    } catch (HibernateException e) {
      if (tx!=null) tx.rollback();
      e.printStackTrace();
    } finally {
      session.close();
    }
  }
}

I ran the database with docker, and the following command:我使用 docker 和以下命令运行数据库:

docker run --name local-mysql -e MYSQL_ROOT_PASSWORD='root' -e MYSQL_USER='test' -e MYSQL_PASSWORD='test' -e MYSQL_DATABASE='test' -d -p 3306:3306 mysql/mysql-server:5.6.49

With this setup, with JDK 1.8, the test always run properly.使用此设置和 JDK 1.8,测试始终可以正常运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM