Java CDI @PersistenceContext 和线程安全

Posted

技术标签:

【中文标题】Java CDI @PersistenceContext 和线程安全【英文标题】:Java CDI @PersistenceContext and thread safety 【发布时间】:2012-06-19 06:50:00 【问题描述】:

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

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

This question 和 this one 似乎是特定于 Spring 的。我正在使用 Jave EE CDI 服务

【问题讨论】:

【参考方案1】:

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

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

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

但是EntityManager注入到 servlet 和单例 bean 是不安全的,因为可能有多个线程可以同时访问它们,从而弄乱了同一个 JDBC 连接。

另见

Mind thread-safety when injecting EntityManager The EntityManager is not thread-safe

【讨论】:

很好的解释,但是当你说“在 EJB 中每个会话 bean 都是池化的,这意味着你不能真正同时从多个线程调用同一个 EJB 的方法”时你错了 - @Singleton EJB 或池大小为 1 的 EJB 具有 bean 管理的并发性,可以有多个线程同时执行 EJB 逻辑。 @StevoSlavić:好吧,我实际上是在说“将 EntityManager 注入 [...] 单例 bean 是不安全的”。如果单例也被视为会话 bean,我将澄清那部分。但是你真的可以为无状态和有状态会话 bean 禁用容器管理的同步吗?我知道你只能对单身人士这样做......【参考方案2】:

虽然 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 的并发访问。

【讨论】:

请注意,这个答案(尽管被接受)实际上不是真的,正如 polbotinka 在另一个答案中提到的那样。如果您对thread safetyJava EE EntityManager 感兴趣,请继续阅读。 @Aquillo,“事实上不是真的”是不正确的,polbotinka 没有提到它。这个答案实际上是正确的,而且切中要害。我能想到的唯一挑剔的是,它将重要性应用于代理身份(“实际上它们共享相同的代理 EntityManager”),这是没有意义的。 polbotinka 谈到 EJB,但问题是关于 CDI!【参考方案3】:

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

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

所以我的结论是:

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

【讨论】:

您在结论点 2 中指的是什么应用服务器? 可能你可以在 Singleton EJB 中使用完全线程安全的 Em,因为你为它的所有方法 (Lock.Write) 保留了基本的 Singleton 行为,这使得所有方法都可以访问,因为它们具有同步修饰符。 “另一方面,EntityManager 有一个容器实现。根据上述内容,它也不应该是线程安全的。” - 你误读了规范。规范在这里不关心代理,它谈论的是一个实际的逻辑 EntityManager,而不是扩展 EntityManager 类的每个物理 java 实例。不过,规范可能会更清晰。他们可能不想深入“上下文代理”领域,只是用“事务范围”来暗示它。 同样,我怀疑“根据规范,绑定与当前 JTA 事务关联的 EntityManager 的代码可能不是线程安全的”是一个正确的结论。 @Black.Jack 我同意,但是对于那些正在考虑这样做的人,请确保您了解性能影响。对于使用数据库的写锁定单例,这意味着一次只能有一个用户使用它,所以如果你真的想这样做,请确保你没有运行频繁、昂贵的查询。也就是说,当无状态 EJB 将消除大多数并发和性能问题时,我无法想象您为什么要这样做。

以上是关于Java CDI @PersistenceContext 和线程安全的主要内容,如果未能解决你的问题,请参考以下文章

CDI Features

Java EE 6:切换到 CDI - 最佳资源

在 Java SE 中拥有 CDI 和 JPA 的最简单方法是啥?

Java CDI @PersistenceContext 和线程安全

Java CDI:具有多个通用参数的装饰器

Servlet CDI