【发布时间】:2012-06-19 06:50:00
【问题描述】:
EntityManager @Inject[ed] 在多个类中是否是线程安全的?
@PersistenceContext(unitName="blah")
private EntityManager em;
This question 和 this one 似乎是特定于 Spring 的。我正在使用 Jave EE CDI 服务
【问题讨论】:
EntityManager @Inject[ed] 在多个类中是否是线程安全的?
@PersistenceContext(unitName="blah")
private EntityManager em;
This question 和 this one 似乎是特定于 Spring 的。我正在使用 Jave EE CDI 服务
【问题讨论】:
令我惊讶的是(在 spring 中使用 jpa 多年后)EntityManager 不是线程安全的。如果您更深入地考虑它,这实际上是可以理解的:EntityManager 只是原生 JPA 实现的包装器,例如Hibernate 中的会话,它又是jdbc 连接的包装器。话虽如此,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 的并发访问。
【讨论】:
thread safety 和Java EE EntityManager 感兴趣,请继续阅读。
我觉得我需要更深入地了解这一点,因为我的第一个答案并非绝对正确。
我将参考 JSR-220 (EJB 3.0)。在 5.2 获取 EntityManager 部分中,您可能会发现:
一个实体管理器不能在多个并发之间共享 执行线程。实体管理器只能在 单线程方式。
嗯,就是这样。您可能会停止阅读此处,除非正确同步,否则永远不要在单例 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 中正常工作,而 EntityManager 注入到无状态 bean 中,但在单例中不能正常工作。
所以我的结论是:
【讨论】: