JPA:EntityManager 的 get-use-close 和延迟加载

Posted

技术标签:

【中文标题】JPA:EntityManager 的 get-use-close 和延迟加载【英文标题】:JPA: get-use-close of EntityManager and lazy loading 【发布时间】:2012-10-09 17:18:15 【问题描述】:

IBM 建议使用 EntityManagers 的最佳实践是获取/使用/关闭。如果 EntityManager 没有关闭,那么同一个 EntityManager 可能会被多个线程使用,这将导致以下错误:

<openjpa-2.1.2-SNAPSHOT-r422266:1179900 fatal general error> org.apache.openjpa.persistence.PersistenceException: Multiple concurrent threads attempted to access a single broker. By default brokers are not thread safe; if you require and/or intend a broker to be accessed by more than one thread, set the openjpa.Multithreaded property to true to override the default behavior. 

如果您加载一个对象,该对象的 OneToMany 集合映射为 fetch=LAZY,如下所示:

public T find(Object id) 
    T t = null;
    EntityManager em = getEm();
    t = em.find(type, id);
    em.close();
    return t;


EntityManager getEm() 
    if(this.em ==null || !this.em.isOpen()) 
        this.em = emf.createEntityManager();            
    
    return this.em;

集合将始终为 null,因为当有人调用 getter 时,EntityManager 已关闭。仅当 fetch 为 EAGER 时才加载集合,但这会导致每次 SQL 连接都很慢。

所以它要么是“多线程”错误,要么是 openjpa.Multithreaded=true,这是一种不好的做法,或者由于每次都需要 SQL 连接,即使不需要集合,它也很慢。有什么方法可以正确地做到这一点,以便通过 Lazy fetch 既快速又仅使用最佳实践完成?

【问题讨论】:

您在使用 JEE 堆栈吗?这会让事情变得更简单 它是与 WebSphere 8 捆绑在一起的 openjpa。我试图只使用纯 JPA。你有什么建议? 将 EM 打包到无状态 EJB 中可以解决线程问题。因为您只能依靠持久性来获取实体的惰性部分,只要它是附加的 - 即在事务中 我不会对服务器的选择有太大的影响。它现在是 WebSphere,但将来可能是 tomcat,所以我试图避免所有 EE 的东西,并使 WAR 尽可能自包含。也许我可以将 openEJB 与应用程序捆绑在一起,但是当我知道它最初会在 WebSphere 上运行时,这听起来是错误的...... 有关 EntityManager 和 Servlet 的最佳实践:***.com/questions/11773975/… 【参考方案1】:

好的,这是我对这个问题进行两天研究后得出的结论。对于不能依赖部署在 Java EE 服务器上并且不能保证单线程访问的应用程序(例如,tomcat 上的 Web 应用程序),最佳实践确实是在方法范围内打开、使用和关闭实体管理器DAO 对象。

这意味着延迟加载在 DAO 之外无法工作。为了解决这个问题,在通过 id 查找一个实体的方法中,需要通过在集合上调用 size() 来获取所有集合,以在实体管理器关闭之前触发获取。这将使 find 方法返回一个完全加载的对象,即使 fetch 是惰性的。

对于返回对象集合的方法,如搜索,在搜索结果中完全加载每个实体会太慢,因此返回的结果是没有子元素的。每当需要查看搜索结果中的某个实体时,都必须通过获取完全加载对象的方法单独加载它。

【讨论】:

【参考方案2】:

好的,不使用 Java EE,您可以创建一个简单的 EntityManagers 池。我使用 StackKeyedObjectPool(来自 Apache Commons Pool)并在需要时创建新的 EntityManager。我有一个借/还接口,池会根据需要自动创建新对象。见http://commons.apache.org/pool/api-1.6/org/apache/commons/pool/impl/StackKeyedObjectPool.html

【讨论】:

【参考方案3】:

实际上,您应该能够让 Websphere 为您注入实体管理器。

@PersistenceContext(unitName = "<whatever>")
private EntityManager em;

您需要访问数据的位置。应用服务器将为您处理线程问题,因为每个 bean 将只处理一个请求,并且该请求将在一个线程上。

【讨论】:

如果我将其设为 EJB 但我希望能够在纯 servlet 容器上运行应用程序。 需要一个 EJB3.1 容器而不是自己处理问题可能不会那么痛苦——尤其是如果您已经为 Websphere 付费的话。如果您没有 Websphere,那么周围有几个开源 servlet 容器。此外,我认为 servlet 容器应该能够注入实体管理器并处理用户事务。【参考方案4】:

我创建了 GitHub 项目,在 servlet(tomcat) 容器中提供自动管理的 EntityManager 生命周期。单线程 http 请求上下文检索相同的实例,并且 EM 在 http 请求结束时自动关闭。这为 servlet 和 jsp 脚本提供了简单的抽象,而无需显式的 try-catch-finally 样板。 https://github.com/Murmur/ScopedEntityManager

【讨论】:

以上是关于JPA:EntityManager 的 get-use-close 和延迟加载的主要内容,如果未能解决你的问题,请参考以下文章

JPA EntityManager 返回没有 entityManager.clear 的陈旧值

如何从 Grails 服务 (JPA + GAE) 中访问 EntityManager

比较JPA的EntityManager接口与Hibernate的Session接口

JPA:EntityManager 的 get-use-close 和延迟加载

Play 2.4 / Ebean / JPA / hibernate-entitymanager 的正确配置是啥?

一个创建函数可以使用 EntityManager (JPA) 处理任何类型的对象吗?