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 safety
和Java 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 和线程安全的主要内容,如果未能解决你的问题,请参考以下文章
在 Java SE 中拥有 CDI 和 JPA 的最简单方法是啥?