使用持久引用持久化对象时,使用JPA遇到数据库死锁

时间:2011-03-25 23:05:41

标签: hibernate postgresql jpa deadlock guice

我一直在努力解决这个问题几天,我找不到一个我很满意的解决方案。我可以通过各种程度的间接来解决这个问题,但这似乎是我应该做的事情,虽然我找到了一些可能有我问题的其他人,但他们似乎没有一个确切的问题。问题或没有提供答案。

虽然我之前使用Hibernate成功完成了这项工作,但我还是无法使用JPA,这正是我现在关注的重点。

初步信息:

  • 使用持久扩展,hibernate(带注释)和c3p0(最多5个连接)的Guice 3.0应用程序。我们从提供程序注入EntityManager,我可以确认EntityManager对象在整个过程中保持与事务对象相同。
  • 设计为Web应用程序的一部分,当前的问题仅发生在使用JUnit和dbUnit的自动化集成测试中。访问是针对单个对象(.in(Scopes.SINGLETON)),但该Singleton对象仅依赖于注入器而@Transactional是线程安全的。
  • 在数据库中有一个文件表,它映射到带注释的FileContainer和一个映射到对象MIMEType的mimetype表。在我们创建新文件的应用程序中,我们首先将MIMEType对象作为NamedQuery检索并保留它。
  • 数据库是PostgreSQL 8.4,使用postgresql-8.4-702.jdbc3.jar。
  • 系统对于失败的情况适度挑剔:我需要生成一个单独的线程并从那里执行访问。

FileContainer对象中mimetype字段的映射是:

@ManyToOne(fetch=FetchType.EAGER, cascade={})  
@JoinColumn(name="mimetype_id", referencedColumnName="id", nullable=false, updatable=true)

MIMEType对象注释如下:

@Entity
@Table(name="mimetypes")  
@org.hibernate.annotations.Immutable  
@Cacheable(true)  
@NamedQuery(name="mt.ext", query="from MIMEType where extension = :ext",  
    hints= {@QueryHint(name="org.hibernate.fetchSize", value="1"),  
            @QueryHint(name="org.hibernate.readOnly", value="true")})

以上所有注释都是javax.persistence版本。

addFile方法使用@com.google.inject.persist.Transactional进行注释。将此更改为在类中使用注入的EntityManager似乎不会更改结果。

创建对象的过程如下:

  1. 从进样器中获取EntityManager和FileDAO。这些都来自提供商。
  2. 通过调用em.createNamedQuery(“mt.ext”,MIMEType.class).setParameter(“ext”,extension).getSingleResult();
  3. 从数据库中获取我们将使用的MIMEType对象。
  4. 创建FileContainer对象并填充它,包括使用我们刚刚检索的对象调用`setMimetype(MIMEType)`
  5. 调用dao.save(fileContainer),在FileContainer对象中设置基本字段,然后调用em.persist(Object)。将其从DAO中拉出并使用先前注入的EntityManager不会改变结果。
  6. 此时EntityManager.flush()发生时(无论发生在何处 - 在事务结束时或之前),INSERT都会发生并且代码会死锁。

    当我检查pg_stat_activity并将其与pg_locks进行比较时,我看到插入语句被“事务中空闲”连接阻塞,并且它处于等待状态。删除MIMEType的插入(并将列设置为允许空值)允许代码正常进行,并且检查MIMEType的值表明它已被正确检索。

    任何想法或想法都表示赞赏。

    编辑:

    这是层次结构,希望它澄清了操作的顺序和发生的事情:

    • 测试线束(初始化注入器并使用运行器将其注入特定测试类)
      • 测试用例
        • 线程已启动
        • 在线程内输入单个对象上的事务块(使用相同的注入器初始化),确认EntityManager个对象在所有DAO对象中都是相同的,并且发生了故障。
        • 通过EntityManager调用或获取injector.getInstance(EntityManager.class)对象始终获取
        • Provider个对象。
        • 完成上述过程,全部在同一个帖子中。
        • 冻结flush()

    代码似乎没有任何应该是线程之间共享的线程本地,并且所有内容都是使用注入器进行初始化的,这对我来说是令人困惑的。

2 个答案:

答案 0 :(得分:3)

我在这里所说的唯一可能是问题的就是:

  

系统在失败的情况下适度挑剔:我需要生成一个单独的线程并从那里执行访问。

Guice-Persist工作单元和事务使用ThreadLocal限定为一个线程。如果你在一个单独的线程上做某事,我肯定能看到类似的事情发生(尽管我不确定究竟是怎么回事)。你能详细解释一下这个单独的线程何时以及如何被使用?从@Transactional方法内部?它是否使用在原始线程上注入的EntityManager

编辑:嗯......我没看到你在做什么有什么问题。似乎工作和事务都在一个线程上完成。不知道那里发生了什么。

答案 1 :(得分:2)

我发现了问题。

在代码的其他地方,我有一个不相关但正在进行的事务,该事务正在@Before方法中启动。出于某种原因,EntityManager阻止该交易完成。当所有内容都在同一个线程中并且用于某些只读操作时,这不是问题,但是当它被拆分成一个单独的线程并尝试写入时,它开始等待另一个事务处理,这在时间上没有完成交易和触及许多相同的表。