如何在 CDI 环境中管理 EntityManager 生命周期(使用 Tomcat)

Posted

技术标签:

【中文标题】如何在 CDI 环境中管理 EntityManager 生命周期(使用 Tomcat)【英文标题】:How to manage EntityManager life-cycle in CDI environment (using Tomcat) 【发布时间】:2013-12-08 14:15:20 【问题描述】:

我正在开发一个应用程序,我已经开始使用CDI 以及JSFJPA。 Web 容器是Tomcat

我对 CDI bean 中的 EntityManager 生命周期感到非常困惑,我需要一个好的建议来清除我脑海中的一些东西。 一般来说,我读到的是EntityManager 应该主要用于Java EE 容器中,使用PersistenceContext 注释注入它。所以容器会关心它的生命。但是,如果你不使用Java EE 容器(如Tomcat),那么我需要管理我的EntityManager 的生活。

使用Tomcat, CDI, JSF and JPA,我现在最好的选择是什么? 我现在正在做的事情如下:

public class EntityManagerFactoryProducer 

    public static final String TEST = "test";

    @Produces
    @ApplicationScoped
    public EntityManagerFactory create() 
        return Persistence.createEntityManagerFactory(TEST);
    

    public void destroy(@Disposes
    EntityManagerFactory factory) 
        factory.close();
    


public class EntityManagerProducer 

    @Inject
    private transient Logger logger;

    @Inject
    private EntityManagerFactory emf;

    @Produces
    public EntityManager create() 
        return emf.createEntityManager();
    

    public void destroy(@Disposes
    EntityManager em) 
        em.close();
        logger.debug(String.format("%s Entity manager was closed", em));
    


@Named
@ViewScoped
@Interceptors(LoggingInterceptor.class)
public class ProductBacking implements Serializable 

    @Inject
    private ProductDAO productDAO;

@ViewScoped
public class ProductDAOImpl implements ProductDAO, Serializable 
    private static final long serialVersionUID = 4806788420578024259L;

    private static final int MAX_RANDOMIZED_ELEMENTS = 3000;

    @Inject
    private transient Logger logger;

    @Inject
    private EntityManager entityManager;

    @Override
    public List<Product> getSuggestedProducts() 
        logger.debug(String.format("%s Entity manager get products", entityManager));

        return entityManager.createQuery("SELECT p FROM Product p ORDER BY random()", Product.class).setMaxResults(
                MAX_RANDOMIZED_ELEMENTS).getResultList();
    

    @Override
    public void saveProduct(Product product) 
        logger.debug(String.format("%s Entity manager save product", entityManager));

        entityManager.getTransaction().begin();
        entityManager.merge(product);
        entityManager.getTransaction().commit();
    

    @PreDestroy
    void destroy() 
        entityManager.close();
    

所以基本上我只是使用普通的CDI 来完成这个。 但是,我不确定这是否是标准的做法,更重要的是,我不知道在 bean 生命结束后如何关闭EntityManager。 作为总结:我最终有许多未关闭的连接(EntityManagers),所以内存泄漏。

有人可以帮助我了解我应该如何进行吗?

【问题讨论】:

你愿意尝试 Tomcat 以外的东西吗? @JoshuaWilson 一般来说是的,但我有点被这个环境所困扰。你会推荐哪一个? TomEE,Glassfish? 如果您所做的只是 servlet 和 jsp,那么 Tomcat 就可以了。当您开始添加 JEE 的所有部分时,您正在构建自己的应用服务器。这样做会带来更大的风险和更高的维护。我会推荐JBoss/Wildfly,它是免费的,它旨在做你想做的事。 【参考方案1】:

这与 CDI 无关。 EntityManager 的生命周期取决于它的类型,可以是:

    容器管理事务, 容器管理扩展, 应用程序管理。

前两个仅在成熟的应用程序服务器中可用。因此,如果您要坚持使用 servlet 容器,则只能选择第 3 个选项。

您必须在应用程序中显式打开和关闭 EM。 这很简单:创建一个应用程序范围的 EntityManagerFactory 实例,将其注入所有 bean。当您需要一个 EM 时,只需创建它,使用然后立即关闭,而无需等待 bean 的上下文结束。 因为在此配置中,打开的 EntityManager 将保留连接,并且使用长寿命的 bean,您将用完连接。 您可以在 ObjectDB 手册的Obtaining a JPA Database Connection 部分找到简单而全面的解释。

【讨论】:

谢谢。是的,你说的是真的。一个问题:如果实体管理器是方法范围的,那么延迟加载不再起作用,对吗?所以我需要从我的实体中禁用所有延迟加载? @Ioan 您不需要禁用延迟加载,您只需确保在需要时(例如通过调用entity.getLazyCollection().size())加载延迟字段,这意味着如果您知道需要这些字段,则必须加载它们。 所以作为最后的结论,我必须坚持方法范围的实体管理器,处理所有与事务相关的事务(beginTransaction、commit、close),如果我需要延迟加载的特定集合,我需要在该集合上调用 size() ,以便在我提交事务并关闭实体 bean 之前强制加载它们。我说的对吗? @loan 是的,除了延迟加载部分)调用 size() 或延迟关联上的任何其他方法只是为了加载它通常被认为是一种不好的做法。当存在与集合或单个实体的惰性关联时,EM 将其隐藏在代理对象后面。该代理负责按需获取数据。如果碰巧您不需要该数据,则加载它是没有意义的,如果您确实需要它,EM 会在您开始使用该延迟加载的集合或实体时自动为您加载它。所以不需要人为触发抓取! @Andrei_I 使用 JPQL 查询或 Criteria API 和 FETCH JOIN:SELECT m FROM Magazine m LEFT JOIN FETCH m.articles 这种方法比调用 size() 有优势。因为您对要获取的内容非常具体,所以 EM 更有可能生成优化的 SQL,尤其是对于复杂的查询。我建议阅读获取策略和“N+1 选择问题”。 Hibernate docs 很好地涵盖了这个主题【参考方案2】:

您可以使用CDI Scopes 维护 CDI bean 状态。 实际上EntityManagerProducer#create() 缺少范围。无论您在 tomact 的 Weld 或 OpenWebBean 中配置/安装什么 CDI 的 RI,您都可以将您的 cdi bean 状态定义为 belwo。

@Produces @RequestScoped
public EntityManager create() 
    return emf.createEntityManager();

你的问题是

1. CDI, JSF and JPA2.  
2. Managing EntityManager lifecycle when using JPA in a non enterprise environment (e.g. Tomcat)

1. CDI、JSF 和 JPA2。

Tomcat 容器不支持开箱即用的 CDI,甚至不支持 JSF,你知道开发人员必须自己提供 JSF jars。JSF 2.2 有新的 CDI 兼容范围 @ViewScoped 这里是 CDI-only @FlowScoped,它不有 @ManagedBean 的等价物。

(1) 真的如果你对使用 CDI 或 CDI+JSF+JPA 最感兴趣,那么将 tomcat 升级到 TomEE 或使用 TomEE。 Tomcat + Java EE = TomEE。Tomcat的Java企业版,有了TomEE你就得到了Tomcat with JPA。

(2) 如果你无法控制升级 tomcat 服务器,在这种情况下你必须这样做 一世。提供 CDI 和其他一些 jar 和配置文件以及 weapp 它自己。 ii.在 tomcat 中安装 CDI(Weld 或 OpenWebBeans 这些都是主要的 CDI 实现)

(3) Tomcat 8。Tomcat 8 与 Java EE 7 保持一致。

2) 管理 EntityManager 生命周期

在非企业环境(例如 Tomcat)或 Java SE 中使用 JPA 时管理 EntityManager 生命周期是一项自定义任务。 在这种情况下,您应该考虑 EntityManager 的正确使用范围和 在处理资源时,确保在不再需要时关闭它们始终很重要。

There are three main types of EntityManagers defined in JPA.

    Container Managed and Transaction Scoped Entity Managers
    Container Managed and Extended Scope Entity Managers
    Application Managed Entity Managers

使用 JPA,我们可以处理两种资源:EntityManager 和事务。 在这种情况下,您应该考虑要使用的 EntityManager 的正确范围。

1. An EntityManager is not a heavyload object.
   There is no need to use the same EntityManger longer than needed,
   You can't use an EntityManager instance for the whole application lifecycle (application scope) for the EntityManager is not Thread-safe)
2. It's not safe to traverse lazy-loaded relationships once the EntityManager is closed (This situation will change as of JPA 2.0).
i.)Method scope (i.e. instantiate/destroy one EntityManager in each business method).
   The method scope is not enough for every situation. There could be some scenarios where you'll need a wide scope, such as the following situations:
   i.  When transactions spread multiple business methods.
   ii. Need to traverse lazy-loaded relationships outside a method (e.g. in a JSF page).
   In method scope be careful to ensure the EntityManger is always closed
  ii.)Request scope (on-demand creation of the EntityManager instance to use within the request service)
   EntityManager per HTTP request strategy with the following features:
    i.  Creation on demand of the EntityManager.
    ii. Lazy closing of the EntityManager. 

The main benefit of this scope is derived from the delayed closing of the EntityManager (it will last as long as a HTTP request is in process).
Every queried entity will be managed till the end of the request and therefore during the presentation phase (the render phase in JSF for instance).

在您的情况下,您使用的是应用程序实体管理器和应用程序管理的事务,这意味着您的代码应该处理事务。简而言之就是:

你打电话:

entityManager.getTransaction().begin(); //to start a transaction

如果成功,您将确保调用

entityManager.getTranasaction().commit(); //to commit changes to database

或者如果失败,您将确保致电:

entityManager.getTransaction().rollBack();

现在假设您有一个容器,它知道何时调用begin(), commit() or rollback(),即容器管理事务。

【讨论】:

感谢您的回答。你为什么说:“在你的情况下,你正在使用应用程序实体管理器”?你是说实体管理器工厂? 因为你得到了像 Persistence.createEntityManagerFactory(TEST) 这样的实体管理器工厂,并得到了实体管理器 emf.getEntityManger()。即使你会得到这样的实体管理器 @PersistenceUnit EntityManagerFactory emf; o 调用 emf.createEntityManager()。这意味着您正在使用应用程序管理的 EntityManager。应用程序负责创建和删除 EntityManager。 啊哈,好吧,现在说得通了。谢谢。 是的,我知道实体管理器在您的代码中没有范围。 emf 具有此范围意味着定义为 @ApplicationScoped 的对象在应用程序期间创建一次。范围未定义容器管理器实体管理器或应用程序管理器实体管理器。范围定义 CDI 基础/Web 应用程序中 CDI bean 的状态/生命周期 @loan 我已经更新了答案。首先我不想把所有的东西都放出来,因为我虽然你很了解容器与实体管理器以及本地资源 tx 和 JTA 事务【参考方案3】:

主要问题是您的实体管理器生产者没有范围。结果,它永远不会被清理。您应该为您的实体经理提供一个范围。

另一件事是 Apache DeltaSpike 已经解决了这个问题。为什么不使用 DeltaSpike? https://deltaspike.apache.org/documentation/jpa.html

【讨论】:

谢谢。你的回答帮助了我。不过,我需要稍微研究一下 deltaspike 框架,看看是否适合我的需求。 好答案。还指向DeltaSpike,非常适合这个场景。【参考方案4】:

你可以配置三种类型的EM

container-managed transactional,
container-managed extended,
application-managed.

我们通常使用容器管理的事务和应用程序管理。我给你举个例子。

对于应用程序管理,通常我们在一种方法中定义一个 EM。

public List<BookingMainDO> retrieve(String key) 
...
        EntityManager em = null;
        try 
            em = emf.createEntityManager();
            Query query = em.createQuery(queryString);      
            //get the resultList of BookingMain 
            result = query.getResultList();
         catch (Exception e) 
            DAOExceptionHandler.handler(dataSource,BookingMainDAO.class, e, queryString);
        finally
            em.close();
        
...

对于容器管理的EM,默认是事务范围的。 您需要在 spring 中使用以下注释进行配置

<context:annotation-config/>

然后在你的 DAO 类中添加下面的注释

@PersistenceContext
private EntityManager em;

然后在每种方法中,您都可以使用 EM 自动注入。 使用事务范围的 EM,persistenceContext 可以在同一事务中的不同方法之间传播。

【讨论】:

问题是关于 CDI。因此,应用程序中不存在spring框架。

以上是关于如何在 CDI 环境中管理 EntityManager 生命周期(使用 Tomcat)的主要内容,如果未能解决你的问题,请参考以下文章

您如何在当前视图(范围)中找到 CDI bean?

CDI 事务管理

180 - flowable-cdi流程bean管理

180 - flowable-cdi流程bean管理

servlet CDI

181 - flowable-cdi流程bean管理流程事件