多个类中的 EntityManager @Inject[ed] 是否是线程安全的?
@PersistenceContext(unitName="blah")
private EntityManager em;
令我惊讶的是(在春季使用jpa多年之后)不是线程安全的。如果你深入思考一下,这实际上是可以理解的:它只是原生 JPA 实现的包装器,例如 Hibernate 中的 session,它反过来又是jdbc连接的包装器。话虽这么说不能是线程安全的,因为它代表一个数据库连接/事务。EntityManager EntityManagerEntityManager
那么为什么它在 Spring 中起作用呢?因为它将目标包装EntityManager在代理中,所以原则上ThreadLocal用于为每个线程保留本地引用。这是必需的,因为 Spring 应用程序构建在单例之上,而 EJB 使用对象池。
在您的情况下,您该如何处理?我不知道cdi,但在 EJB 中,每个无状态和有状态会话 bean 都是池化的,这意味着您不能真正同时从多个线程调用同一个 EJB 的方法。因此EntityManager从不同时使用。话虽如此,注入EntityManager是安全的,至少注入到无状态和有状态会话 bean 中。
然而,注入EntityManagerservlet 和单例 bean 并不安全,因为可能有多个线程可以同时访问它们,从而弄乱了相同的 JDBC 连接。
尽管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的并发访问。
我觉得我需要更深入地了解这一点,因为我的第一个答案并非绝对正确。
我将参考JSR-220 (EJB 3.0)。在第5.2 节获取 EntityManager中,您可能会发现:
实体管理器不能在多个并发执行的线程之间共享。实体管理器只能以单线程方式访问。
就是这样。除非正确同步,否则您可能会停止阅读此处并且永远不要在单例 bean 中使用EntityManager 。
但我相信规范中存在混淆。实际上有两种不同的EntityManager实现。第一个是提供者实现(比如 Hibernate),它不一定是线程安全的。
另一方面,EntityManager有一个容器实现。根据上述内容,这也不应该是线程安全的。但是容器的实现充当代理并将所有调用委托给真实提供者的EntityManager。
因此,在5.9 Runtime Contracts between the Container and Persistence Provider的规范中更进一步:
对于事务范围的持久性上下文的管理,如果没有与 JTA 事务关联的 EntityManager:当第一次使用 Persistence-ContextType.TRANSACTION 调用实体管理器时,容器通过调用 EntityManagerFactory.createEntityManager 创建一个新的实体管理器在 JTA 事务中执行的业务方法的范围内。
这反过来意味着每个启动的事务都会有一个不同的EntityManager实例。根据5.3,创建EntityManager的代码是安全的:
EntityManagerFactory 接口的方法是线程安全的。
但是如果有一个与 JTA 事务关联的EntityManager怎么办?根据规范,绑定与当前 JTA 事务关联的EntityManager的代码可能不是线程安全的。
但是我真的想不出一个应用程序服务器实现可以与注入到无状态 bean中的EntityManager一起正常工作,而不能在单例中正确工作。
所以我的结论是: