JPA 2.0 如何处理死锁(Eclipselink JPA2.0 MySQL)

Posted

技术标签:

【中文标题】JPA 2.0 如何处理死锁(Eclipselink JPA2.0 MySQL)【英文标题】:JPA 2.0 How to handle deadlock (Eclipselink JPA2.0 MySQL) 【发布时间】:2013-12-03 04:26:21 【问题描述】:

我尝试添加/获取/更新代码,并且我使用了 EclipseLink 提供程序和 JPA2.0。和 mysql

下面的代码抛出一个错误,说发生了死锁。这个问题是随机发生的。我想知道如何处理死锁。

这是错误信息:

    javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
    Internal Exception: java.sql.SQLException: null,  message from server: "Deadlock found when trying to get lock; try restarting transaction"
    Error Code: 1213
    Call: UPDATE activitylog SET timestampdate = ? WHERE (logid = ?)
        bind => [2013-11-19 20:10:38.583, 1]
    Query: UpdateObjectQuery(test.ActivityLog@dd3314)

这是我正在尝试的代码:

    public class TestMain 
        public static void main(String[] args) 
            for(int j = 0; j < 10; j ++) 
                Thread thread = new Thread(new Runnable() 

                    @Override
                    public void run() 
                        for (int i = 0; i < 200; i++) 
                            ActivityLogDAO activityLogDAO = new ActivityLogDAO();
                            try 
                                ActivityLog theActivityLog = new ActivityLog();
                                theActivityLog.setTimestampdate(new Date());
                                theActivityLog.setMyId(i);
                                activityLogDAO.insert(theActivityLog);

                                ActivityLog activityLog = activityLogDAO.getActivityLog(theActivityLog);

                                activityLog.setTimestampdate(new Date());
                                activityLogDAO.update(activityLog);

                             catch (Exception e) 
                                e.printStackTrace();
                            

                        
                    
                );
                thread.start();
            
        
    

这里是实体类

    @Entity
    @Table(name="activitylog")
    public class ActivityLog implements Serializable 

        private static final long serialVersionUID = 1L;

        @Id
        @GeneratedValue(strategy=GenerationType.SEQUENCE)
        @Column(name="logid")
        private long logid;

        @Column(name="myid")
        private long lMyId;

        @Temporal(TemporalType.TIMESTAMP)
        @Column(name="timestampdate", nullable=true)
        private Date timestampdate;


        public long getMyId() 
            return lMyId;
        

        public void setMyId(long lMyId) 
            this.lMyId = lMyId;
        

        public long getLogid() 
            return logid;
        

        public void setLogid(long logid) 
            this.logid = logid;
        

        public Date getTimestampdate() 
            return timestampdate;
        

        public void setTimestampdate(Date timestampdate) 
            this.timestampdate = timestampdate;
        

    

这是我的 DAO 类:

    public class ActivityLogDAO 
        private EntityManagerFactory _entityManagerFactory = null;
        private EntityManager _entityManager = null;

        public ActivityLogDAO() 
            _entityManagerFactory = Persistence.createEntityManagerFactory("MyTestOnLock");
            _entityManager = _entityManagerFactory.createEntityManager();
        

        protected EntityManager getEntityManager() 
            return _entityManager;
        

        protected void setEntityManager(EntityManager _entityManager) 
            this._entityManager = _entityManager;
        

        public ActivityLog insert(ActivityLog theActivityLog) throws Exception 
            if(null == theActivityLog) 
                throw new Exception("Invalid ActivityLog Object");
            

            if(false == getEntityManager().getTransaction().isActive()) 
                getEntityManager().getTransaction().begin();
            

            System.out.println("inserting");
            getEntityManager().persist(theActivityLog);
            getEntityManager().getTransaction().commit();
            System.out.println("inserted");

            return theActivityLog;
        

        public ActivityLog getActivityLog(ActivityLog theActivityLog) throws Exception 
            if(null == theActivityLog) 
                throw new Exception("Invalid ActivityLog Object");
            

            if(false == getEntityManager().getTransaction().isActive()) 
                getEntityManager().getTransaction().begin();
            

            System.out.println("trying to get object");
            Query query = getEntityManager().createQuery("SELECT m FROM ActivityLog m WHERE m.lMyId = :lMyId");
            query.setParameter("lMyId", theActivityLog.getMyId());
            //deadlock happens here.
            @SuppressWarnings("unchecked")
            List<ActivityLog> resultList = query.getResultList();
            System.out.println(resultList.size());
            System.out.println("got object");
            if(null == resultList || 0 == resultList.size()) 
                return null;
             else 
                return resultList.get(0);
            
        

        public ActivityLog update(ActivityLog theActivityLog) throws Exception 
            if(null == theActivityLog) 
                throw new Exception("Invalid ActivityLog Object");
            

            if(false == getEntityManager().getTransaction().isActive()) 
                getEntityManager().getTransaction().begin();
            
            System.out.println("trying to update object");
            Query query = getEntityManager().createQuery("UPDATE ActivityLog m SET m.timestampdate = :timestampdate WHERE m.lMyId = :lMyId");
            query.setParameter("lMyId", theActivityLog.getMyId());
            query.setParameter("timestampdate", theActivityLog.getTimestampdate());

            int executeUpdate = query.executeUpdate();
            getEntityManager().getTransaction().commit();
            System.out.println("object updted.");

            if(0 == executeUpdate) 
                return null;
            

            return theActivityLog;
        
    

这是我的persistance.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
        <persistence-unit name="MyTestOnLock">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

        <class>test.ActivityLog</class>


        <properties>
    <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"></property>
    <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/locktest"></property>
    <property name="javax.persistence.jdbc.user" value="root"></property>
    <property name="javax.persistence.jdbc.password" value="root"></property>

    <!-- EclipseLink should create the database schema automatically   -->
    <property name="eclipselink.ddl-generation" value="create-tables" /> 
    <property name="eclipselink.ddl-generation.output-mode" value="database" />
    <property name="eclipselink.id-validation" value="NULL"></property>
    <property name="eclipselink.logging.level" value="FINE"/>
    <property name="javax.persistence.lock.timeout" value="100"/>
    <property name="eclipselink.order-updates" value="true"/>
    <property name="eclipselink.connection-pool.sequence" value="max" />
    <property name="eclipselink.ddl-generation.output-mode" value="database" />
    <property name="eclipselink.target-database" value="MySQL" />

    </properties>

    </persistence-unit>

    </persistence>

当 AcitivityDAO 尝试更新时发生死锁。是否有处理或避免死锁问题的原因?

感谢任何帮助!


我收到以下错误:

      javax.persistence.PersistenceException: java.lang.NullPointerException

      javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
      Internal Exception: java.sql.SQLException: Deadlock found when trying to get lock; Try restarting transaction,  message from server: "Lock wait timeout exceeded; try restarting transaction"
      Error Code: 1205
      Call: UPDATE activitylog SET timestampdate = ? WHERE (myid = ?)
  bind => [2013-11-20 16:54:09.646, 0]
      Query: UpdateAllQuery(referenceClass=ActivityLog sql="UPDATE activitylog SET timestampdate = ? WHERE (myid = ?)")

我使用了@Chris Ridal 指定的相同代码。

这里是代码:基本上我尝试多次运行 MainTest 类。

    public class MainTest 
        public static void main(String[] args) 
            updateActivityLog();
        

        private static void updateActivityLog() 
            final PersistenceController persistenceController = new PersistenceController(Persistence.createEntityManagerFactory("MyTestOnLock"));
            for (int i = 0; i < 100; i++) 
                    try 
                        for(int j = 0; j < 200; j++) 
                            ActivityLog theActivityLog = new ActivityLog();
                            theActivityLog.setMyId(j);
                            theActivityLog.setTimestampdate(new Date());
                            persistenceController.update(theActivityLog);
                        

                     catch (Exception e) 
                        e.printStackTrace();
                     
            
            persistenceController.commitAndClose();
        
    


    public class PersistenceController 
        private EntityManager manager;

        public PersistenceController(EntityManagerFactory factory)
        
            /*
             * Normally you want to split your work up into separate transactions
             * (ie new entity managers), in a logical way which will depend on how
             * your application works. This class will do that for you if you keep
             * your factory. Note that factory's are expensive to create but entity
             * managers are cheap to create.
             */
            manager = factory.createEntityManager();
            manager.getTransaction().begin();
        

        // Call ONCE on an object after creating it, it will stay in sync with the database even when you change it remotely
        public void persist(Serializable entityObj)
        
            manager.persist(entityObj);
            manager.flush();
        

        // Call to sync with database (even though you might not actually see the objects in the database until you commit)
        public void flush()
        
            manager.flush();
        

        /*
         * Call when you are done with your unit of work to commit the DB changes
         */
        public void commitAndClose()
        
            manager.getTransaction().commit();
            manager.close();
        

        public ActivityLog getActivityLog(ActivityLog theActivityLog) throws Exception 
            if(null == theActivityLog) 
                throw new Exception("Invalid ActivityLog Object");
            
            if(false == manager.getTransaction().isActive()) 
                manager.getTransaction().begin();
            

            System.out.println("trying to get object");
            Query query = manager.createQuery("SELECT m FROM ActivityLog m WHERE m.lMyId = :lMyId");
            query.setParameter("lMyId", theActivityLog.getMyId());

            @SuppressWarnings("unchecked")
            List<ActivityLog> resultList = query.getResultList();
            System.out.println(resultList.size());
            System.out.println("got object");
            if(null == resultList || 0 == resultList.size()) 
                return null;
             else 
                return resultList.get(0);
            
        

        public ActivityLog update(ActivityLog theActivityLog) throws Exception 
            if(null == theActivityLog) 
                throw new Exception("Invalid ActivityLog Object");
            
            if(false == manager.getTransaction().isActive()) 
                manager.getTransaction().begin();
            
            ActivityLog activityLog = getActivityLog(theActivityLog);
            activityLog.setTimestampdate(theActivityLog.getTimestampdate());
            persist(activityLog);
            return theActivityLog;
        

    

我是否必须为每个数据库插入或合并或更新或删除获取 EntityManager?见下面的代码,我没有看到死锁发生。请确认。

public class ActivityLogDAO 
    private EntityManagerFactory _entityManagerFactory = null;
    private EntityManager _entityManager = null;

    public ActivityLogDAO() 
        _entityManagerFactory = Persistence.createEntityManagerFactory("MyTestOnLock");
    

    protected EntityManager getEntityManager() 
        return _entityManager;
    

    protected void setEntityManager(EntityManager _entityManager) 
        this._entityManager = _entityManager;
    

    public ActivityLog insert(ActivityLog theActivityLog) throws Exception 
        if(null == theActivityLog) 
            throw new Exception("Invalid ActivityLog Object");
        

        _entityManager = _entityManagerFactory.createEntityManager();

        if(false == getEntityManager().getTransaction().isActive()) 
            getEntityManager().getTransaction().begin();
        

        System.out.println("inserting");
        getEntityManager().persist(theActivityLog);
        getEntityManager().getTransaction().commit();
        System.out.println("inserted");

        return theActivityLog;
    

    public ActivityLog getActivityLog(ActivityLog theActivityLog) throws Exception 
        if(null == theActivityLog) 
            throw new Exception("Invalid ActivityLog Object");
        
        _entityManager = _entityManagerFactory.createEntityManager();

        if(false == getEntityManager().getTransaction().isActive()) 
            getEntityManager().getTransaction().begin();
        

        System.out.println("trying to get object");
        Query query = getEntityManager().createQuery("SELECT m FROM ActivityLog m WHERE m.lMyId = :lMyId");
        query.setParameter("lMyId", theActivityLog.getMyId());
        //deadlock happens here.
        @SuppressWarnings("unchecked")
        List<ActivityLog> resultList = query.getResultList();
        System.out.println(resultList.size());
        System.out.println("got object");
        if(null == resultList || 0 == resultList.size()) 
            return null;
         else 
            return resultList.get(0);
        
    

    public ActivityLog update(ActivityLog theActivityLog) throws Exception 
        if(null == theActivityLog) 
            throw new Exception("Invalid ActivityLog Object");
        
        _entityManager = _entityManagerFactory.createEntityManager();

        if(false == getEntityManager().getTransaction().isActive()) 
            getEntityManager().getTransaction().begin();
        
        System.out.println("trying to update object");
        Query query = getEntityManager().createQuery("UPDATE ActivityLog m SET m.timestampdate = :timestampdate WHERE m.lMyId = :lMyId");
        query.setParameter("lMyId", theActivityLog.getMyId());
        query.setParameter("timestampdate", theActivityLog.getTimestampdate());

        int executeUpdate = query.executeUpdate();
        getEntityManager().getTransaction().commit();
        System.out.println("object updted.");

        if(0 == executeUpdate) 
            return null;
        

        return theActivityLog;
    

【问题讨论】:

如果你还没有,也许看看使用 jpa 的事务以及事务如何帮助避免死锁。有很好的信息。确保您关注事务何时释放锁。 从显示的代码来看,这似乎是不可能的,因为每个线程只会触及它在每次迭代中创建的行。在重现问题的运行中,检查还有什么正在向 logid =1 的对象发出语句(或错误中显示的任何值)。 【参考方案1】:

一般you do not need to use DAO's when using JPA。

相反,您可能希望使用这样的类(未经测试),带上您自己的 EntityManagerFactory:

public class PersistenceController

    private EntityManager manager;

    public PersistenceController(EntityManagerFactory factory)
    
        /*
         * Normally you want to split your work up into separate transactions
         * (ie new entity managers), in a logical way which will depend on how
         * your application works. This class will do that for you if you keep
         * your factory. Note that factory's are expensive to create but entity
         * managers are cheap to create.
         */
        manager = factory.createEntityManager();
        manager.getTransaction().begin();
    

    // Call ONCE on an object after creating it, it will stay in sync with the database even when you change it remotely
    public void persist(Serializable entityObj)
    
        manager.persist(entityObj);
    

    // Call to sync with database (even though you might not actually see the objects in the database until you commit)
    public void flush()
    
        manager.flush();
    

    /*
     * Call when you are done with your unit of work to commit the DB changes
     */
    public void commitAndClose()
    
        manager.getTransaction().commit();
        manager.close();
    


要使用它,您可以在创建对象时调用 persist(entityObj),在创建对象后调用 flush() 与数据库同步(如果需要),在完成后调用 commitAndClose()。将PersistenceController 保存在您需要持久化对象或使用其其他操作时可以发布到它的位置。

现在您的事务不会同时发生,也不会出现死锁。

注意:在生产代码中,您将使用更多的异常管理并将您的工作拆分为不同的 EntityManager 事务,如果您在逻辑上处置和创建此 PersistenceController 类,则此类将为您执行此操作。

【讨论】:

在上面示例中更新行后,我使用了flush(),即使发生了死锁。 问题出在您的 DAO 实现中。您手动管理交易。你有没有像 Chris 说的那样停止使用 DAO?如果您遇到死锁,这可能意味着您正在尝试从现有事务中启动新事务。您应该在您想要完成的工作的外部边界开始事务。永远不要在另一个事务中启动一个事务。甚至更好:使用一个容器来为你管理交易。试试 Spring Data。 @mwhs,我只是将其称为 DAO,但代码看起来与 Chris 所说的相似。但是可以修改代码。而且我还必须基于 MyId 而不是基于 LogId 来获取对象 ActivityLog 来更新,因为作为用户,我不会知道 JPA 生成的 LogId 来查找()对象。 Chris 给出的示例没有 merge() 或 update()。如何根据列而不是根据生成的 Id 更新行,这不会导致死锁 知道ID没关系。你有对象引用。一旦你在一个对象上调用了persist,它将与持久层保持同步。无需在一个会话中再次调用“更新”。您开始并提交您正在做的工作中的事务。不要那样做。在其他所有事情之前/之后开始和停止交易(在您的情况下)。在您的 DAO/helper 类的 insert 方法中NOT 启动/提交事务。不要这样做。 @mwhs 理解,但正如你所说,我有对象引用,如果你看一下 getActivityLog() 方法。返回的对象是否会引用实际的对象,所以我可以更改对象?【参考方案2】:

For what it's worth:

死锁是事务数据库中的一个经典问题,但它们并不危险,除非它们过于频繁以至于您根本无法运行某些事务。通常,您必须编写应用程序,以便它们始终准备好在事务因死锁而回滚时重新发出事务

有时无法避免死锁情况。但是,为了降低死锁的可能性或频率,不要对数据访问代码进行深入调查。这个问题与导致死锁的操作顺序有关。避免死锁的一种正式方法是始终以相同的顺序锁定和释放资源。说起来容易做起来难:)

有趣的资源:@​​987654322@

您可以使用SHOW ENGINE INNODB STATUS 跟踪死锁中涉及哪些并发事务(活动事务列在“事务”部分,并提供详细信息)。

【讨论】:

以上是关于JPA 2.0 如何处理死锁(Eclipselink JPA2.0 MySQL)的主要内容,如果未能解决你的问题,请参考以下文章

如何处理SQL Server死锁问题

如何处理SQL Server死锁问题

如何处理SQL Server死锁问题

MySQL(InnoDB)是如何处理死锁的

如何处理锁(JPA)?

如何处理指向通用接口的指针的 JPA 注释