MySQL休眠异常

Posted

技术标签:

【中文标题】MySQL休眠异常【英文标题】:MySQL Hibernate Exception 【发布时间】:2014-08-22 21:51:35 【问题描述】:

我最近从 mysql-connector-java 5.1.22 更新到 5.1.23。之后,我的应用程序在更新或删除持久对象时不断崩溃。

最高 5.1.22(包括 5.1.22)的 Mysql 连接器工作正常。来自 5.1.23(包括 5.1.23)的 MySQL 连接器无法正常工作。

问题的原因似乎是我用于乐观锁定的时间戳。带有版本的乐观锁定似乎工作正常。但我无法更改整个应用程序,我需要时间戳字段。

5.1.23 的更新日志指出以下错误修正:

如果时间戳值是通过准备好的语句参数传递的, 小数秒精度被剥离,即使底层 字段(例如 VARCHAR(255))可以存储完整值。一种解决方法 是在指定时将时间戳值转换为字符串 准备好的语句参数,例如 prepped_stmt.setString(1,time_stamp.toString()。这部分已修复 在 5.1.19 中,但该修复并未涵盖该设置的情况 useLegacyDatetimeCode=true。 (错误 #11750017、错误 #40279、错误 #60584)

我怀疑这是我的问题的原因。任何想法如何解决这个问题?我附加了一个导致错误的简单示例代码。

---例外

Jul 02, 2014 9:18:34 AM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations 4.0.4.Final
Jul 02, 2014 9:18:34 AM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core 4.3.5.Final
Jul 02, 2014 9:18:34 AM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Jul 02, 2014 9:18:34 AM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Jul 02, 2014 9:18:34 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH000402: Using Hibernate built-in connection pool (not for production use!)
Jul 02, 2014 9:18:34 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/hibernatedb?useFastDateParsing=false]
Jul 02, 2014 9:18:34 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000046: Connection properties: user=root, password=****
Jul 02, 2014 9:18:34 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH000006: Autocommit mode: false
Jul 02, 2014 9:18:34 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 1 (min=1)
Jul 02, 2014 9:18:34 AM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Jul 02, 2014 9:18:34 AM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
Jul 02, 2014 9:18:34 AM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
Jul 02, 2014 9:18:35 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000227: Running hbm2ddl schema export
Jul 02, 2014 9:18:35 AM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000230: Schema export complete
MyPersistentObject (2014-07-02 09:18:35.0) : First persistent object
MyPersistentObject (2014-07-02 09:18:35.0) : A second persistent object
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [MyPersistentObject#2]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3403)
    at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3630)
    at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:114)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
    at HibernateExample.manipulatePersistentObjects(HibernateExample.java:143)
    at HibernateExample.main(HibernateExample.java:15)
MyPersistentObject (2014-07-02 09:18:35.0) : First persistent object
MyPersistentObject (2014-07-02 09:18:35.0) : A second persistent object
Jul 02, 2014 9:18:35 AM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/hibernatedb?useFastDateParsing=false]

---主类

import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.service.ServiceRegistry;

public class HibernateExample 
    private final org.hibernate.SessionFactory _sessionFactory;
    private MyPersistentObject _myPersistentObject1;
    private MyPersistentObject _myPersistentObject2;
    private MyPersistentObject _myPersistentObject3;

    final static public void main(final String arguments[]) 
        HibernateExample example;
        example = new HibernateExample();
        example.createPersistentObjects();
        example.readAndDisplayPersistentObjects();
        example.manipulatePersistentObjects();
        example.readAndDisplayPersistentObjects();
        example.cleanup();
    

    private HibernateExample() 
        org.hibernate.cfg.Configuration configuration;

        java.util.logging.Logger.getLogger("org.hibernate").setLevel(java.util.logging.Level.SEVERE); // Supress
        // Hibernate's
        // excessive
        // output

        configuration = new org.hibernate.cfg.Configuration();
        configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); // Customize
        // this
        // for
        // your
        // particular
        // RDBMS
        configuration.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver"); // Customize
        // this
        // for
        // your
        // particular
        // RDBMS

        // configuration.setProperty("hibernate.connection.url",
        // "jdbc:mysql://localhost:3306/hibernatedb?useLegacyDatetimeCode=true");
        // // Customize

        configuration.setProperty("hibernate.connection.url",
                "jdbc:mysql://localhost:3306/hibernatedb?useFastDateParsing=false"); // Customize

        // this
        // for
        // your
        // particular
        // RDBMS
        configuration.setProperty("hibernate.connection.username", "root"); // Customize
        // this
        // for
        // your
        // particular
        // RDBMS
        configuration.setProperty("hibernate.connection.password", "root"); // Customize
        // this
        // for
        // your
        // particular
        // RDBMS
        // installation
        configuration.setProperty("hibernate.connection.pool_size", "1"); // Customize
        // this
        // for
        // your
        // particular
        // RDBMS
        // installation
        configuration.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.internal.NoCacheProvider"); // This
        // is
        // not
        // ready
        // for
        // prime-time
        configuration.setProperty("hibernate.show_sql", "false"); // Tell
        // hibernate
        // to not
        // echo the
        // SQL
        configuration.setProperty("hibernate.hbm2ddl.auto", "create");

        configuration.setProperty("useLegacyDatetimeCode", "true");

        configuration.addAnnotatedClass(MyPersistentObject.class);

        // configuration.configure();
        final ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(
                configuration.getProperties()).build();
        this._sessionFactory = configuration.buildSessionFactory(serviceRegistry);

    

    final private void createPersistentObjects() 
        org.hibernate.Session session;
        boolean committed;
        org.hibernate.Transaction transaction;

        session = this._sessionFactory.openSession();

        try 
            committed = false;
            transaction = session.beginTransaction();

            try 
                this._myPersistentObject1 = new MyPersistentObject("First persistent object", new java.util.Date());
                session.save(this._myPersistentObject1);

                this._myPersistentObject2 = new MyPersistentObject("A second persistent object", new java.util.Date());
                session.save(this._myPersistentObject2);

                transaction.commit();
                session.flush();
                committed = true;
             finally 
                if (!committed) 
                    transaction.rollback();
                
            
         finally 
            session.close();
        
    

    final private void manipulatePersistentObjects() 
        org.hibernate.Session session;
        org.hibernate.Transaction transaction;

        session = this._sessionFactory.openSession();

        try 
            transaction = session.beginTransaction();

            this._myPersistentObject3 = new MyPersistentObject("A third persistent object", new java.util.Date());
            // session.save(this._myPersistentObject3);

            session.delete(this._myPersistentObject2);

            transaction.commit();
            session.flush();
         catch (final Exception e) 
            e.printStackTrace();
         finally 

            session.close();
        
    

    final private void readAndDisplayPersistentObjects() 
        org.hibernate.Session session;
        java.util.List<MyPersistentObject> result;

        session = this._sessionFactory.openSession();

        try 
            session.beginTransaction();

            result = (session.createQuery("from MyPersistentObject").list());

            for (final MyPersistentObject persistentObject : result) 
                System.out.println("MyPersistentObject (" + persistentObject.getDate() + ") : "
                        + persistentObject.getTitle());
            

            session.getTransaction().commit();
            session.flush();
         catch (final Exception e) 
            e.printStackTrace();
         finally 
            session.close();
        
    

    final private void cleanup() 
        if (this._sessionFactory != null) 
            this._sessionFactory.close();
        
    

---对象

import java.util.Calendar;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Version;

@javax.persistence.Entity
public class MyPersistentObject 

    @javax.persistence.Id
    @javax.persistence.GeneratedValue(generator = "increment")
    @org.hibernate.annotations.GenericGenerator(name = "increment", strategy = "increment")
    private Long _persistenceID;
    private String _title;
    @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
    @javax.persistence.Column(name = "OBJECT_DATE")
    private java.util.Date _date;

    @Version
    private Calendar timestemp;

    // Hibernate needs a no-argument constructor
    private MyPersistentObject() 
    

    // for application use, to create new persistent objects
    public MyPersistentObject(final String title, final java.util.Date date) 
        this._title = title;
        this._date = date;
    

    public java.util.Date getDate() 
        return this._date;
    

    public String getTitle() 
        return this._title;
    

    @PrePersist
    protected void onCreate() 
        System.out.println("create");
    

    @PreUpdate
    protected void onUpdate() 
        System.out.println("update");
    

---pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>Hibernate34Migrate</groupId>
    <artifactId>Hibernate34Migrate</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.5.Final</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>4.3.5.Final</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.31</version>
        </dependency>
    </dependencies>
</project>

【问题讨论】:

【参考方案1】:

感谢 Devabc,一个很好的解释。事实上,所描述的问题已经被报告为休眠问题

https://hibernate.atlassian.net/browse/HHH-3822 和相关的https://hibernate.atlassian.net/browse/HHH-9444,但更有趣的是,建议对 mysql DB > 5.6.4 进行修复。基本上,建议是使用新的 Dialect 实现 MySQL5InnoDBDialect 的扩展并注册一个转换器

public class ImprovedMySQL56Dialect extends MySQL5InnoDBDialect 

    public ImprovedMySQL56Dialect() 
        super();
        // http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
        registerColumnType( Types.TIMESTAMP, "datetime(6)" );
    

它在https://hibernate.atlassian.net/browse/HHH-8401中描述

【讨论】:

【参考方案2】:

背景资料

MySQL

在 MySQL 5.6.4 之前,MySQL 确实支持一些能够在几分之一秒内操作的函数,但 MySQL 不支持存储小数:

http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html

但是,当 MySQL 将值存储到任何时间数据类型的列中时,它会丢弃任何小数部分并且不存储它。

MySQL 5.6.4 及更高版本扩展了对 TIME、DATETIME 和 TIMESTAMP 值的小数秒支持,精度高达微秒(6 位):

要定义包含小数秒部分的列,请使用语法 >type_name(fsp),其中 type_name 是 TIME、DATETIME 或 TIMESTAMP,fsp 是小数秒精度。例如:

创建表 t1 (t TIME(3), dt DATETIME(6));

MySQL Connector/J

http://dev.mysql.com/doc/relnotes/connector-j/en/news-5-1-23.html

如果时间戳值通过准备好的语句参数传递,则小数秒精度被剥离,即使基础字段(例如 VARCHAR(255))可以存储完整值。一种解决方法是在指定准备好的语句参数时将时间戳值转换为字符串,例如 prepped_stmt.setString(1,time_stamp.toString()。这在 5.1.19 中已部分修复,但该修复并未涵盖这种情况设置 useLegacyDatetimeCode=true。(Bug #11750017、Bug #40279、Bug #60584)

问题:Hibernate 使用 3 个小数位忽略 MySQL

当 Hibernate 将支持版本控制的对象插入数据库时​​,它会执行以下操作:

2014-nov-07;20:32:05.775 TRACE org.hibernate.engine.internal.Versioning - Seeding: 2014-11-07 20:32:05.774
2014-nov-07;20:32:05.786 TRACE org.hibernate.persister.entity.AbstractEntityPersister - Version: 7-11-14 20:32
2014-nov-07;20:32:05.787 DEBUG org.hibernate.SQL - insert into A (b_id, version) values (?, ?)
Hibernate: insert into A (b_id, version) values (?, ?)
2014-nov-07;20:32:05.799 TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [null]
2014-nov-07;20:32:05.799 TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [TIMESTAMP] - [2014-11-07 20:32:05.774]

因此,Hibernate 似乎在内部使用 3 位数字来表示小数秒精度,并且还会尝试将小数存储到数据库中,即使 MySQL 版本不支持它也是如此。

使用较旧的 Connector/J 时,会去除分数并存储日期时间(第二精度)。 当实体 A 发生变化时,Hibernate 会执行乐观锁定,并且会发生这样的事情:

2014-nov-07;20:32:05.822 DEBUG org.hibernate.internal.util.EntityPrinter - entities.Bid=27
2014-nov-07;20:32:05.822 DEBUG org.hibernate.internal.util.EntityPrinter - entities.Ab=entities.B#27, id=29, version=2014-11-07 20:32:05.774
2014-nov-07;20:32:05.825 TRACE org.hibernate.persister.entity.AbstractEntityPersister - Updating entity: [entities.A#29]
2014-nov-07;20:32:05.825 TRACE org.hibernate.persister.entity.AbstractEntityPersister - Existing version: 7-11-14 20:32 -> New version:7-11-14 20:32
2014-nov-07;20:32:05.825 DEBUG org.hibernate.SQL - update A set b_id=?, version=? where id=? and version=?
Hibernate: update A set b_id=?, version=? where id=? and version=?
2014-nov-07;20:32:05.826 TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [27]
2014-nov-07;20:32:05.826 TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [TIMESTAMP] - [2014-11-07 20:32:05.82]
2014-nov-07;20:32:05.826 TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - [29]
2014-nov-07;20:32:05.826 TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [TIMESTAMP] - [2014-11-07 20:32:05.774]

(不确定为什么 Hibernate 将“20:32:05.82”显示为绑定参数,我希望它是更高版本,所以它可能会截断零并表示“20:32:05.820”,这与对数分数。)

但是因为旧的 Connector/J 在插入语句和 Hibernate 版本控制更新语句(其中从 SQL-WHERE 子句中去除了分数)中都去掉了分数,所以它还没有导致问题.

但是当使用新版本 (>= 5.1.23) 的 Connector/J 时,分数会被保留并到达数据库。这基本上还不是问题,它甚至比没有分数更好,因为它在并发方面更安全。 但是当数据库列的类型为“DATETIME”(Hibernate 默认),即“DATETIME(0)”,没有分数时,就会出现 Hibernate 版本控制问题,因为

update A set b_id=?, version=? where id=? and version=?

会导致0更新,导致Hibernate认为“行被另一个事务更新或删除”。

解决方案 1(已测试):将分数添加到列中

将列类型从“DATETIME”更改为“DATETIME(3)”。这可能是一个安全的操作,因为 MySQL 只是将分数 000 附加到日期时间。

为了完整起见,还要将其添加到版本化时间戳实例变量中:

@Column(columnDefinition = "datetime(3)")

方案2(已测试):让MySQL生成日期版本

如果你不想在列中添加分数,但让数据库生成版本没有问题,那么你可以使用以下方法:

将以下内容添加到经过验证的时间戳实例变量中:

@Generated(GenerationTime.ALWAYS)

这将让 Hibernate 使用数据库列来生成版本。

然后将列类型从“日期时间”更改为:

DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

此解决方案不使用分数,因此在并发方面很危险,即使在 Hibernate 中结合使用 MySQL Connector/J

解决方案 3:使用专用版本号而不是日期时间

见http://docs.jboss.org/hibernate/orm/4.3/devguide/en-US/html/ch05.html#d5e1246

这在并发方面是安全的,但如果您仍然需要更新日期时间,则不太方便。

其他解决方案(未经测试)

另一种解决方案可能是实现您自己的 org.hibernate.usertype.UserVersionType。

https://docs.jboss.org/hibernate/core/4.3/javadocs/org/hibernate/usertype/UserVersionType.html

https://developer.jboss.org/wiki/UserTypeForNon-defaultTimeZone

新的 Hibernate 应用程序的另一个解决方案可能是修改 MySQL 方言类以自动使用带分数的日期时间。

讨论

很难说这是否是一个 Hibernate 错误。最好的解决方案是 Hibernate 是否会自动测试 MySQL 环境是否支持小数日期时间列,如果支持,则默认为“datetime(3)”列而不是“datetime”。 或者当 MySQL 环境不支持 Hibernate 时,应该从其内部版本控制中删除分数,但这在并发方面有点危险。

注意

当使用 TemporalType.TIMESTAMP 时,Hibernates MySQL5InnoDBDialect 不使用 MySQL 的 TIMESTAMP,而是使用 DATETIME。

【讨论】:

当我们将 MySQL 实例从 5.5 升级到 5.6 后开始遇到这些休眠的 StaleObjectStateException 时,这个答案确实帮助了我的团队。我们遵循解决方案 1 并将 DATETIME 列更改为 DATETIME(3)。【参考方案3】:

    我建议你使用更可靠的增量版本:

    @Version
    private int version;
    

    Hibernate 可以处理删除分离的实体(在操作PersistentObjects() 中,this._myPersistentObject2 是分离的,在不同的关闭会话中加载),但 JPA 不允许这种行为。

    您不应该在提交后刷新。如果您使用的是自动刷新模式,您甚至不必调用刷新,因为提交也应该触发刷新。

【讨论】:

谢谢,我知道增量版本更可靠,但正如我所说,我无法改变它。错误发生在刷新或关闭而不是提交时?!?。似乎与在 5.1.22 上工作的分离实体一起工作,在 5.1.23 上不起作用。

以上是关于MySQL休眠异常的主要内容,如果未能解决你的问题,请参考以下文章

创建表时的休眠异常

Spring Boot休眠类型异常创建带有JSON字段的表

数据库休眠并导致异常

休眠非唯一对象异常

休眠:- 未找到本机查询异常

休眠映射异常:无法确定类型