Java CDI @PersistenceContext和线程安全

时间:2012-06-16 12:47:21

标签: java java-ee-6 cdi

EntityManager @Inject [ed]在多个类的线程安全中如下所示?

@PersistenceContext(unitName="blah")
private EntityManager em;

This问题和this one似乎是针对Spring的。我正在使用Jave EE CDI服务

3 个答案:

答案 0 :(得分:18)

令我惊讶的是(在中使用多年后)EntityManager 并非线程安全。如果你仔细考虑一下,这实际上是可以理解的:EntityManager只是本机JPA实现的包装,例如: Hibernate中的会话,它反过来是连接的包装。据说EntityManager不能是线程安全的,因为它代表一个数据库连接/事务。

那为什么它在Spring工作?因为它将目标EntityManager包装在代理中,原则上使用ThreadLocal来保持每个线程的本地引用。这是必需的,因为Spring应用程序构建在单例之上,而EJB使用对象池。

你怎么能处理你的情况呢?我不知道但是在EJB中,每个无状态和有状态会话bean都是池化的,这意味着您无法在同一时间从多个线程中真正调用同一EJB的方法。因此EntityManager永远不会同时使用。话虽这么说,注入EntityManager是安全的,至少是无状态和有状态会话bean。

然而向servlet和singleton bean注入EntityManager并不安全,因为可能有多个线程同时访问它们,弄乱了相同的JDBC连接。

另见

答案 1 :(得分:12)

虽然 EntityManager 实现本身不是线程安全的,但 Java EE 容器会注入一个代理,该代理将所有方法调用委托给绑定了 EntityManager 的事务。因此,每个事务都使用它自己的 EntityManager 实例。对于至少事务范围的持久化上下文(默认情况下),这是正确的。

如果容器会在每个bean中注入一个新的 EntityManager 实例,则以下内容不起作用:

@Stateless
public class Repository1 {
   @EJB
   private Repository2 rep2;

   @PersistenceContext(unitName="blah", type = PersistenceContextType.TRANSACTION)
   private EntityManager em;

   @TransactionAttribute
   public void doSomething() {
      // Do something with em
      rep2.doSomethingAgainInTheSameTransaction();
   }
}

@Stateless
public class Repository2 {
   @PersistenceContext(unitName="blah", type = PersistenceContextType.TRANSACTION)
   private EntityManager em;

   @TransactionAttribute
   public void doSomethingAgainInTheSameTransaction() {
      // Do something with em
   }
}

doSomething-> doSomethingAgainInTheSameTransaction 调用在单个事务中发生,因此bean必须共享相同的 EntityManager 。实际上,它们共享相同的代理 EntityManager ,它将调用委托给相同的持久化上下文。

所以你合法使用单个bean中的 EntityManager ,如下所示:

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class Repository {
   @PersistenceContext(unitName="blah", type = PersistenceContextType.TRANSACTION)
   private EntityManager em;
}

另一个证明是 EntityManager javadoc中没有提及线程安全性。因此,当您留在 Java EE 容器中时,您不应该关心对 EntityManager 的并发访问。

答案 2 :(得分:8)

我觉得我需要深入研究这个问题,因为我的第一个答案并非绝对正确。

我将参考 JSR-220 。在 5.2获取EntityManager 部分,您可以找到:

  

实体经理可能不会同时在多个人之间共享   执行线程。实体经理只能在a中访问   单线程方式。

那就是它。你可以在这里停止阅读,除非正确同步,否则永远不要在单例bean中使用 EntityManager

但我相信规范存在混淆。实际上有两种不同的 EntityManager 实现。第一个是提供程序实现(说Hibernate),它没有义务是线程安全的。

另一方面,有一个 EntityManager 的容器实现。根据以上内容,这也不应该是线程安全的。但容器的实现充当代理,并将所有调用委托给真实提供者的 EntityManager

在容器和持久性之间 5.9运行时契约的规范中进一步说明 提供商

  

用于管理事务范围的持久性上下文,如果   没有与JTA事务关联的EntityManager:   容器通过调用创建新的实体管理器   第一次调用时EntityManagerFactory.createEntityManager   发生Persistence-ContextType.TRANSACTION的实体管理器   在JTA中执行的业务方法的范围内   事务。

这反过来意味着每个启动的事务都会有一个不同的 EntityManager 实例。根据 5.3 创建 EntityManager 的代码是安全的:

  

EntityManagerFactory接口的方法是线程安全的。

但是如果有一个与JTA交易相关联的 EntityManager 怎么办?根据规范,绑定与当前JTA事务关联的 EntityManager 的代码可能不是线程安全的。

但我真的不能想到一个应用程序服务器实现能够正确地将 EntityManager 注入到无状态bean中而在单例中不正确。

所以我的结论是:

  1. 如果您想严格遵循 JSR-220 ,请不要在单身人士中使用 EntityManager ,直到同步对其的访问权。
  2. 我个人会继续在单例中使用 EntityManager ,因为我的应用服务器实现与它完美配合。在执行此操作之前,您可能需要检查实现。