21

多个类中的 EntityManager @Inject[ed] 是否是线程安全的?

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

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

4

3 回答 3

22

令我惊讶的是(在多年之后)不是线程安全的。如果你深入思考一下,这实际上是可以理解的:它只是原生 JPA 实现的包装器,例如 Hibernate 中的 session,它反过来又是连接的包装器。话虽这么说不能是线程安全的,因为它代表一个数据库连接/事务。EntityManager EntityManagerEntityManager

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

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

然而,注入EntityManagerservlet 和单例 bean 并不安全,因为可能有多个线程可以同时访问它们,从而弄乱了相同的 JDBC 连接。

也可以看看

于 2012-06-16T13:11:32.993 回答
11

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

于 2012-11-28T14:40:08.060 回答
8

我觉得我需要更深入地了解这一点,因为我的第一个答案并非绝对正确。

我将参考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一起正常工作,而不能在单例中正确工作。

所以我的结论是:

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