Java CDI @PersistenceContext和线程安全

aus*_*ser 23 java cdi java-ee-6

在muliple类threadsafe中,EntityManager @Inject [ed]如下所示?

@PersistenceContext(unitName="blah")
private EntityManager em;
Run Code Online (Sandbox Code Playgroud)

这个问题和这个问题似乎是针对Spring的.我正在使用Jave EE CDI服务

Tom*_*icz 19

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

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

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

但是注入EntityManagerservlet和singleton bean并不安全,因为几个线程可能同时访问它们,弄乱了相同的JDBC连接.

也可以看看

  • 很好的解释,但是在说"在EJB中每个会话bean被合并,这意味着您无法在同一时间从多个线程中真正调用同一EJB的方法"时出错了 - @Singleton EJB或EJB大小为1的池,其中bean托管并发可以有多个线程同时执行EJB逻辑. (2认同)

小智 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
   }
}
Run Code Online (Sandbox Code Playgroud)

doSomething-> doSomethingAgainInTheSameTransaction调用在单个事务中发生,因此bean必须共享相同的EntityManager.实际上,它们共享相同的代理EntityManager,它将调用委托给相同的持久化上下文.

所以你合法使用单例bean中的EntityManager,如下所示:

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

另一个证据是EntityManager javadoc中没有提及线程安全性.因此,当您留在Java EE容器中时,您不应该关心对EntityManager的并发访问.

  • 请注意,这个答案(尽管被接受)事实上并非如此,正如polbotinka在另一个答案中提到的那样.如果您对"Java EE EntityManager"的"线程安全"感兴趣,请继续阅读. (3认同)

小智 8

我觉得我需要深入研究这个因为我的第一个答案并非绝对正确.

我将参考JSR-220.在5.2获取EntityManager中,您可能会发现:

实体管理器可能不会在多个并发执行的线程之间共享.只能以单线程方式访问实体管理器.

那就是它.您可以在这里停止阅读,除非正确同步,否则永远不要在单例bean中使用EntityManager.

但我相信规范存在混淆.实际上有两种不同的EntityManager实现.第一个是提供程序实现(说Hibernate),它没有义务是线程安全的.

另一方面,有一个EntityManager的容器实现.根据以上内容,这也不应该是线程安全的.但容器的实现充当代理并将所有调用委托给真实提供者的EntityManager.

因此,容器和持久性提供程序之间的5.9运行时契约中的规范进一步说明:

对于事务范围的持久化上下文的管理,如果没有已与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,因为我的应用程序服务器实现与它完美配合.在执行此操作之前,您可能需要检查实现.

  • 可能您可以在Singleton EJB中使用完全线程安全的Em,因为您保留了其所有方法的基本Singleton行为(Lock.Write),这使所有方法都具有同步修饰符就可以访问。 (2认同)