Java CDI @PersistenceContext和线程安全

在muliple类threadsafe中,EntityManager @Inject [ed]如下所示?

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

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

尽管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的并发访问。

令我惊讶的是(在spring使用jpa多年后), EntityManager 不是线程安全的 。 如果你更深入地思考它,这实际上是可以理解的: EntityManager只是本机JPA实现的包装器,例如Hibernate中的会话,它反过来是jdbc连接的包装器。 也就是说, EntityManager不能是线程安全的,因为它代表一个数据库连接/事务。

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

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

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

也可以看看

  • 注入EntityManager时注意线程安全性
  • EntityManager不是线程安全的

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

我将参考JSR-220 。 在5.2获取EntityManager中,您可能会发现:

实体管理器可能不会在多个并发执行的线程之间共享。 只能以单线程方式访问实体管理器。

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

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

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

因此,容器和持久性提供程序之间的5.9运行时契约中的规范进一步说明:

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

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

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

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

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

所以我的结论是:

  1. 如果你想严格遵循JSR-220,那么在同步对它的访问之前永远不要在单例中使用EntityManager
  2. 我个人将继续在单例中使用EntityManager ,因为我的应用程序服务器实现与它完美配合。 在执行此操作之前,您可能需要检查实现。