多个类中的 EntityManager @Inject[ed] 是否是线程安全的?
@PersistenceContext(unitName="blah")
private EntityManager em;
令我惊讶的是(在春季使用jpa多年之后)不是线程安全的。如果你深入思考一下,这实际上是可以理解的:它只是原生 JPA 实现的包装器,例如 Hibernate 中的 session,它反过来又是jdbc连接的包装器。话虽这么说不能是线程安全的,因为它代表一个数据库连接/事务。EntityManager
EntityManager
EntityManager
那么为什么它在 Spring 中起作用呢?因为它将目标包装EntityManager
在代理中,所以原则上ThreadLocal
用于为每个线程保留本地引用。这是必需的,因为 Spring 应用程序构建在单例之上,而 EJB 使用对象池。
在您的情况下,您该如何处理?我不知道cdi,但在 EJB 中,每个无状态和有状态会话 bean 都是池化的,这意味着您不能真正同时从多个线程调用同一个 EJB 的方法。因此EntityManager
从不同时使用。话虽如此,注入EntityManager
是安全的,至少注入到无状态和有状态会话 bean 中。
然而,注入EntityManager
servlet 和单例 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一起正常工作,而不能在单例中正确工作。
所以我的结论是: