JPA 死锁与@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
JPA deadlock with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
在我的应用程序中,我遇到了死锁问题,不知道是否以及如何处理它。
我在 glassfish 服务器上使用 JPA 2.1(带有 eclipselink)。
有两个EJB。 OuterBean
应该将 LogEntry
写入数据库并在循环中调用 InnerBean
。 InnerBean
本身
应该写一个 LogEntry
并做一些其他的事情(对其他实体进行更多的数据库操作)。的调用
InnerBean#execute()
彼此独立,这意味着如果一个方法失败(回滚),其他方法应该
保持 运行。因此 InnerBean#execute()
在它自己的事务中运行。
当执行下面的代码时,我得到一个 java.sql.SQLException: Lock wait timeout exeeded; try restarting transaction
在 MySQL 和 java.sql.SQLSyntaxErrorException: ORA-02049: timeout: distributed transaction waiting for lock
下
在 Oracle 下(Postgres 只是等待永恒;可能是因为数据库配置错误)。
我不是 database/JPA 专家,但我想问题是两个事务想要写入同一个数据表。我不做的
理解是存在一个问题,因为这些数据库操作是应该独立于每个的插入
其他。有没有一种方法可以实现这个用例(我是否需要使用 bean 管理的事务,是否有任何我可以使用的注释,我可以在 em.persist(logEntry) 之后强制在 OuterBean#execute()
中提交; 并且在循环之前,以便无论如何释放事务锁)?
@Stateless
public class OuterBean
{
@PersistenceContext(unitName = "PU_LOGGER")
private EntityManager em;
@EJB
private InnerBean innerBean;
public void execute()
{
LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage("OuterBean#execute()");
em.persist(logEntry);
for(int i = 0; i < 10; ++i)
{
innerBean.execute();
}
}
}
@Stateless
public class InnerBean
{
@PersistenceContext(unitName = "PU_LOGGER")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void execute()
{
LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage("InnerBean#execute()" + Math.random());
em.persist(logEntry);
}
}
编辑
这是生成的日志
FINER: client acquired: 1990452734
FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 38847372
FINEST: persist() operation called on: LogEntry{id=null, message=OuterBean#execute(), date=Mon Jul 18 09:00:27 CEST 2016}.
FINER: TX beginTransaction, status=STATUS_ACTIVE
FINEST: Connection acquired from connection pool [default].
FINEST: Execute query DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + #PREALLOC_SIZE WHERE SEQ_NAME = #SEQ_NAME")
FINEST: reconnecting to external connection pool
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [2 parameters bound]
FINEST: Execute query ValueReadQuery(name="SEQUENCE" sql="SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = #SEQ_NAME")
FINE: SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
bind => [1 parameter bound]
FINEST: local sequencing preallocation for SEQ_GEN: objects: 50 , first: 301, last: 350
FINEST: assign sequence to the object (301 -> LogEntry{id=null, message=OuterBean#execute(), date=Mon Jul 18 09:00:27 CEST 2016})
FINER: client acquired: 187184807
FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 2098992041
FINEST: persist() operation called on: LogEntry{id=null, message=InnerBean#execute()0.3957184758563761, date=Mon Jul 18 09:00:28 CEST 2016}.
FINER: TX beginTransaction, status=STATUS_ACTIVE
FINEST: Connection acquired from connection pool [default].
FINEST: Execute query DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?")
FINEST: reconnecting to external connection pool
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [2 parameters bound]
好的,我已经解决了问题。答案隐藏在我的问题中:
can I force a commit in OuterBean#execute() after the
em.persist(logEntry); and before the loop
我创建了一个单独的 EJB 仅用于持久化 LogEntry,运行 它在新事务本身中。
@Stateless
public class OuterBean
{
@EJB
private SeparateLoggingBean separateLoggingBean;
@EJB
private InnerBean innerBean;
public void execute()
{
LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage("OuterBean#execute()");
separateLoggingBean.persistLogEntry(logEntry);
for(int i = 0; i < 10; ++i)
{
innerBean.execute();
}
System.out.println("!ready!");
}
}
@Stateless
public class SeparateLoggingBean
{
@PersistenceContext(unitName = "PU_LOGGER")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void persistLogEntry(LogEntry logEntry)
{
em.persist(logEntry);
}
}
无论如何,我认为@Chris 的第二条评论是正确的。问题似乎是 eclipselink 序列生成中的死锁。
似乎有一个解决方案(http://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/persistenceproperties_ref.htm#BABIDAGH),但我没有让它正常工作。
在提到的link中据说属性
<property name="eclipselink.connection-pool.sequence" value="true"/>
需要放在persistence.xml中。不幸的是,在我的测试中,是否使用 属性 没有任何区别。对于这个众所周知的问题,我发现了几个例子。通常一些过时的属性是从旧的 eclipselink 版本中使用的,我在当前 (2.6) 文档中没有找到这些属性。如果我确实需要指定一个单独的非 jta 连接池,或者如果我指定 eclipselink.connection-pool.sequence
属性,eclipselink 是否自行管理池,我也不清楚。
好吧,如果能找到更多关于这方面的信息就好了,但我很高兴我的应用程序至少可以正常工作。
在我的应用程序中,我遇到了死锁问题,不知道是否以及如何处理它。 我在 glassfish 服务器上使用 JPA 2.1(带有 eclipselink)。
有两个EJB。 OuterBean
应该将 LogEntry
写入数据库并在循环中调用 InnerBean
。 InnerBean
本身
应该写一个 LogEntry
并做一些其他的事情(对其他实体进行更多的数据库操作)。的调用
InnerBean#execute()
彼此独立,这意味着如果一个方法失败(回滚),其他方法应该
保持 运行。因此 InnerBean#execute()
在它自己的事务中运行。
当执行下面的代码时,我得到一个 java.sql.SQLException: Lock wait timeout exeeded; try restarting transaction
在 MySQL 和 java.sql.SQLSyntaxErrorException: ORA-02049: timeout: distributed transaction waiting for lock
下
在 Oracle 下(Postgres 只是等待永恒;可能是因为数据库配置错误)。
我不是 database/JPA 专家,但我想问题是两个事务想要写入同一个数据表。我不做的
理解是存在一个问题,因为这些数据库操作是应该独立于每个的插入
其他。有没有一种方法可以实现这个用例(我是否需要使用 bean 管理的事务,是否有任何我可以使用的注释,我可以在 em.persist(logEntry) 之后强制在 OuterBean#execute()
中提交; 并且在循环之前,以便无论如何释放事务锁)?
@Stateless
public class OuterBean
{
@PersistenceContext(unitName = "PU_LOGGER")
private EntityManager em;
@EJB
private InnerBean innerBean;
public void execute()
{
LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage("OuterBean#execute()");
em.persist(logEntry);
for(int i = 0; i < 10; ++i)
{
innerBean.execute();
}
}
}
@Stateless
public class InnerBean
{
@PersistenceContext(unitName = "PU_LOGGER")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void execute()
{
LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage("InnerBean#execute()" + Math.random());
em.persist(logEntry);
}
}
编辑
这是生成的日志
FINER: client acquired: 1990452734
FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 38847372
FINEST: persist() operation called on: LogEntry{id=null, message=OuterBean#execute(), date=Mon Jul 18 09:00:27 CEST 2016}.
FINER: TX beginTransaction, status=STATUS_ACTIVE
FINEST: Connection acquired from connection pool [default].
FINEST: Execute query DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + #PREALLOC_SIZE WHERE SEQ_NAME = #SEQ_NAME")
FINEST: reconnecting to external connection pool
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [2 parameters bound]
FINEST: Execute query ValueReadQuery(name="SEQUENCE" sql="SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = #SEQ_NAME")
FINE: SELECT SEQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
bind => [1 parameter bound]
FINEST: local sequencing preallocation for SEQ_GEN: objects: 50 , first: 301, last: 350
FINEST: assign sequence to the object (301 -> LogEntry{id=null, message=OuterBean#execute(), date=Mon Jul 18 09:00:27 CEST 2016})
FINER: client acquired: 187184807
FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 2098992041
FINEST: persist() operation called on: LogEntry{id=null, message=InnerBean#execute()0.3957184758563761, date=Mon Jul 18 09:00:28 CEST 2016}.
FINER: TX beginTransaction, status=STATUS_ACTIVE
FINEST: Connection acquired from connection pool [default].
FINEST: Execute query DataModifyQuery(name="SEQUENCE" sql="UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?")
FINEST: reconnecting to external connection pool
FINE: UPDATE SEQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
bind => [2 parameters bound]
好的,我已经解决了问题。答案隐藏在我的问题中:
can I force a commit in OuterBean#execute() after the em.persist(logEntry); and before the loop
我创建了一个单独的 EJB 仅用于持久化 LogEntry,运行 它在新事务本身中。
@Stateless
public class OuterBean
{
@EJB
private SeparateLoggingBean separateLoggingBean;
@EJB
private InnerBean innerBean;
public void execute()
{
LogEntry logEntry = new LogEntry();
logEntry.setDate(new Date());
logEntry.setMessage("OuterBean#execute()");
separateLoggingBean.persistLogEntry(logEntry);
for(int i = 0; i < 10; ++i)
{
innerBean.execute();
}
System.out.println("!ready!");
}
}
@Stateless
public class SeparateLoggingBean
{
@PersistenceContext(unitName = "PU_LOGGER")
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void persistLogEntry(LogEntry logEntry)
{
em.persist(logEntry);
}
}
无论如何,我认为@Chris 的第二条评论是正确的。问题似乎是 eclipselink 序列生成中的死锁。 似乎有一个解决方案(http://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/persistenceproperties_ref.htm#BABIDAGH),但我没有让它正常工作。 在提到的link中据说属性
<property name="eclipselink.connection-pool.sequence" value="true"/>
需要放在persistence.xml中。不幸的是,在我的测试中,是否使用 属性 没有任何区别。对于这个众所周知的问题,我发现了几个例子。通常一些过时的属性是从旧的 eclipselink 版本中使用的,我在当前 (2.6) 文档中没有找到这些属性。如果我确实需要指定一个单独的非 jta 连接池,或者如果我指定 eclipselink.connection-pool.sequence
属性,eclipselink 是否自行管理池,我也不清楚。
好吧,如果能找到更多关于这方面的信息就好了,但我很高兴我的应用程序至少可以正常工作。