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)的主要内容,如果未能解决你的问题,请参考以下文章