Java CDI@PersistenceContext和线程安全

Java CDI@PersistenceContext和线程安全,java,java-ee-6,cdi,jpa,spring,jdbc,Java,Java Ee 6,Cdi,Jpa,Spring,Jdbc,在muliple类中,EntityManager@Inject[ed]是否是线程安全的 @PersistenceContext(unitName="blah") private EntityManager em; 这个问题似乎是针对Spring的。我正在使用Jave EE CDI服务(在中使用多年后)令我大吃一惊的是,EntityManager不是线程安全的。如果您更深入地思考,这实际上是可以理解的:EntityManager只是本地JPA实现的包装,例如Hibernate中的会话,它反过来又

在muliple类中,EntityManager@Inject[ed]是否是线程安全的

@PersistenceContext(unitName="blah")
private EntityManager em;
这个问题似乎是针对Spring的。我正在使用Jave EE CDI服务

(在
中使用多年后)令我大吃一惊的是,EntityManager
不是线程安全的。如果您更深入地思考,这实际上是可以理解的:
EntityManager
只是本地JPA实现的包装,例如Hibernate中的会话,它反过来又是连接的包装。也就是说,EntityManager不能是线程安全的,因为它代表一个数据库连接/事务

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

在你的情况下,你如何处理?我不知道,但在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->doSomethingaain发生在单个事务中,因此bean必须共享相同的EntityManager。实际上,它们共享同一个代理EntityManager,该代理将调用委托给相同的持久性上下文

因此,您在singleton Bean中合法使用EntityManager,如下所示:

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class Repository {
   @PersistenceContext(unitName="blah", type = PersistenceContextType.TRANSACTION)
   private EntityManager em;
}

另一个证据是,EntityManagerjavadoc中没有提到线程安全。因此,当您呆在JavaEE容器中时,您不应该关心对EntityManager的并发访问

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

我将参考JSR-220(ejb3.0)
。在第5.2节“获取实体管理器”中,您可以找到:

实体管理器不能在多个实体之间同时共享 执行线程。实体管理器只能在数据库中访问 单线程方式

就这样。除非正确同步,否则您可以停止阅读这里的内容,并且永远不要在单例bean中使用EntityManager

但我认为规范中存在一个混乱。实际上有两种不同的EntityManager实现。第一个是提供程序实现(称为Hibernate),它不必是线程安全的


另一方面,存在EntityManager的容器实现。根据上述规定,它也不应该是线程安全的。但容器的实现充当代理,并将所有调用委托给真正提供者的EntityManager

因此,在5.9规范中,容器和持久性之间的运行时契约更进一步 提供者

用于管理事务范围的持久性上下文,如果 没有EntityManager已与JTA事务关联: 容器通过调用 EntityManagerFactory.createEntityManager首次调用 发生持久性为-ContextType.TRANSACTION的实体管理器 在JTA中执行的业务方法的范围内 交易

这意味着,对于每个启动的事务,将有一个不同的EntityManager实例。根据5.3,创建实体管理器的代码是安全的:

EntityManagerFactory接口的方法是线程安全的

但是,如果存在与JTA事务相关联的EntityManager呢?根据规范,绑定与当前JTA事务关联的EntityManager的代码可能不是线程安全的

但是我真的想不出一个应用服务器实现能够正确地与注入无状态bean的EntityManager一起工作,而不能正确地在单例中工作

因此,我的结论是:

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

  • 很好的解释,但是当您声明“在EJB中,每个会话bean都是池的,这意味着您不能同时从多个线程调用同一EJB的方法”-@Singleton EJB或池大小为1的EJB(具有bean管理的并发性)可以有多个线程同时执行EJB逻辑。@StevoSlavić:好,我实际上是在说“将EntityManager注入[…]单例bean是不安全的”。如果是单身人士,我会澄清这一部分