使用@TransactionAttribute进行JPA死锁(TransactionAttributeType.REQUIRES_NEW)

时间:2016-07-15 08:56:35

标签: jpa transactions locking eclipselink deadlock

在我的应用程序中,我遇到了死锁问题,不知道是否以及如何处理它。 我在glassfish服务器上使用JPA 2.1(带有eclipselink)。

有两个EJB。 OuterBean应该向数据库写LogEntry并在循环中调用InnerBeanInnerBean本身 应该写一个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等待永恒;可能是因为配置错误的数据库)。

我不是数据库/ 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]

1 个答案:

答案 0 :(得分:1)

好的,我已经解决了这个问题。它的答案隐藏在我的问题中:

  

我可以强制提交OuterBean#execute()之后的提交   em.persist(logEntry);在循环之前

我创建了一个单独的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),但我没有让它正常工作。 在提到的链接中,据说属性

<property name="eclipselink.connection-pool.sequence" value="true"/>

需要放在persistence.xml中。不幸的是,在我的测试中,如果我使用该属性,它没有任何区别。我找到了这个众所周知的问题的几个例子。通常一些过时的属性是从旧的eclipselink版本中使用的,我在当前的(2.6)文档中没有找到。我不清楚是否需要指定一个单独的非jta连接池,或者如果我指定eclipselink.connection-pool.sequence属性,eclipselink是自己管理池。

很高兴能找到更多相关信息,但我很高兴我的申请至少有用。