@ApplicationScoped CDI bean 和 @PersistenceContext - 这安全吗?

Posted

技术标签:

【中文标题】@ApplicationScoped CDI bean 和 @PersistenceContext - 这安全吗?【英文标题】:@ApplicationScoped CDI bean and @PersistenceContext - is this safe? 【发布时间】:2012-12-02 21:09:50 【问题描述】:

用 CDI 做这样的事情安全吗?

@Named
@ApplicationScoped
public class DAO 

   @PersistenceContext
   private EntityManager entityManager;


我了解EntityManager 本身不是线程安全的,因此不应在像@ApplicationScoped 这样的共享全局上下文中使用。但是,由于带有@PersistenceContext 的注入对象实际上是一个围绕底层EntityManager 的线程感知包装器,这样可以吗?

我看过其他关于该主题的帖子,但无法为这个特定案例找出权威答案。例如:

Java CDI @PersistenceContext and thread safety

例如,与 @Stateless 一起使用似乎是安全的 - 但我不确定这是因为 @Stateless 的工作方式,还是因为 @PersistenceContext 本身的某些内在因素。

编辑 我困惑的根源是@PersistenceContext 注入的EntityManager 包装器似乎知道当前线程,以便确定是否已经有一个事务在进行中。所以也许我把线程意识和线程安全混淆了,它们是两个不同的东西。

【问题讨论】:

【参考方案1】:

我很确定在这种情况下 CDI 不会为实体管理器创建上下文代理。毕竟,它会在什么范围内?您可能想要类似于假设的@ThreadScoped 或只是@RequestScoped,但@PersistenceContext 不是CDI 注释,CDI 不会修改其语义。

所以这里发生的是 Java EE 6 平台“托管 bean”注入,它类似于在 Servlet 中注入实体管理器。这两种情况都为您提供了一个不能直接使用线程安全的实例。

例如,与 @Stateless 一起使用看起来是安全的 - 但我不确定这是因为 @Stateless 的工作方式,还是因为 @PersistenceContext 本身固有的某些东西。

这是因为@Stateless 的工作方式。对无状态 bean 上的方法的每个请求(调用)都由容器路由到唯一的实例。容器保证没有两个线程在同一个 bean 中处于活动状态。

使用 CDI,您可以通过将实体管理器封装在请求范围的 bean 中并将其注入到应用程序范围的 bean 中,从而为每个请求获得类似的效果:

import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@RequestScoped
public class EntityManagerProvider 

    @PersistenceContext
    private EntityManager entityManager;

    public EntityManager getEntityManager() 
        return entityManager;
    


将此注入到您之前注入实体管理器的 bean 中:

@Named
@ApplicationScoped
public class DAO 

   @Inject
   private EntityManagerProvider entityManagerProvider;


这将为您提供每个请求的唯一实体管理器。您也可以轻松地将其转换为生产者方法,因此您不必在注入的提供者上调用 getEntityManager()

【讨论】:

在应用范围内注入请求范围是不是有一些限制? @AkselWillgert >在应用范围内注入请求范围没有限制吗? - 不,CDI 部分从中汲取灵感的本机 JSF 托管 bean 有这个限制。 CDI 修复了这个问题,这由 CDI 中的 C 表示 -> 注入(当由 CDI 管理时)是上下文感知的。在这种情况下,即使 bean 是应用程序范围的,注入的代理也会解析为调用方请求范围内的实例。 @ArjanTijms - 谢谢,我想我明白了我的问题。我将线程感知与线程安全混为一谈。我觉得@PersistenceContext 包装器必须知道当前线程才能适当地加入或启动新的 JTA 事务。但这并不意味着生成的对象的状态是线程安全的。听起来我说得对吗? It's because of the way @Stateless works. Every request (call) to a method on a stateless bean is routed by the container to a unique instance. 这不是真的,无状态 bean 经常被池化。 “无状态”不是指 bean 本身的生命周期,而是指客户端和 bean 之间的“对话状态”,特别是缺少这种状态。它是无状态的,就像 HTTP 是无状态的一样。 引用 EJB 3.2 规范 4.3.9.2 无状态会话 Bean:Since stateless session bean instances are typically pooled, the time of the client’s invocation of the create method need not have any direct relationship to the container’s invocation of the PostConstruct/ejbCreate method on the stateless session bean instance.【参考方案2】:

这种方法怎么样:

@RequestScoped 类 EntityManagerProducer @PersistenceContext 私有实体管理器实体管理器; @生产 @RequestScoped 公共实体管理器生产实体管理器() 返回这个实体管理器;

在任何范围的 CDI Beans 中:

@注入 私有实体管理器实体管理器;

应该在每个请求中创建实体管理器,当它通过一个它注入的 CDI bean 时创建一个新的实体管理器。在注入的 CDI Bean 中,您将获得一个在请求中使用的实体管理器。如果需要交易,实体经理将加入它。因此,您在每个请求中都会获得一个新的实体管理器,这意味着最后一切都会被清理,并且您不会遇到线程冲突。简单易行,不是吗?

【讨论】:

以上是关于@ApplicationScoped CDI bean 和 @PersistenceContext - 这安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

JavaEE6 DAO:应该是@Stateless 还是@ApplicationScoped?

多次构造 Eager ApplicationScoped 托管 bean

@ApplicationScoped JSF 托管 bean 的并发性

JEE6 @ApplicationScoped bean 和并发

区别:@SessionScoped vs @Stateful 和 @ApplicationScoped vs @Singleton [关闭]

Servlet CDI