JPA 延迟加载

Posted

技术标签:

【中文标题】JPA 延迟加载【英文标题】:JPA Lazy Loading 【发布时间】:2014-03-06 01:22:18 【问题描述】:

我在 JPA 实体中遇到延迟加载属性的问题。我阅读了许多类似的问题,但它们与 spring 或 hibernate 有关,并且它们的答案要么不适用,要么没有帮助。

应用程序是 JEE,在 Wildfly 应用程序服务器上运行 JPA2.1。有两个实体,DAO 会话 bean 和将其组合在一起的 servlet:

@Entity
@Table(name = "base_user")
public class User implements Serializable 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    int id;

    @OneToMany(fetch=FetchType.LAZY, mappedBy="user")
    List<OAuthLogin> oauthLogins;




@Entity
@Table(name = "oauth_login")
public class OAuthLogin implements Serializable 
    @ManyToOne
    @JoinColumn(name="user_id", nullable=false)
    User user;



@Stateless(name = "UserDAOEJB")
public class UserDAO 
    @PersistenceContext(unitName="OAUTHDEMO")
    EntityManager em;

    public User findById(int id) 
        User entity;
    entity = em.find(User.class, id);
        return entity;
    



public class SaveUserServlet extends HttpServlet 
    @EJB
    UserDAO userDAO;

    @Transactional
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
        User user = new User(name);
        user.setEmail(email);
        System.out.println("Persisting user " + user);
        userDAO.persist(user);

        OAuthLogin fbLogin1 = new OAuthLogin(user, OAuthProvider.FACEBOOK, "1501791394");
        loginDAO.persist(fbLogin1);

        User user2 = userDAO.findById(user.getId());
        List<OAuthLogin> oauthLogins = user2.getOauthLogins();

当我运行这段代码时,它失败了:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: cz.literak.demo.oauth.model.entity.User.oauthLogins, could not initialize proxy - no Session
org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:572)
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:212)
org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:551)
org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:140)
org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:294)
cz.literak.demo.oauth.servlets.SaveUserServlet.doPost(SaveUserServlet.java:66)

我在 WebLogic/JPA1 中使用了非常相似的模式,并且运行顺畅。任何想法?谢谢

PS。这是一个 JPA 应用程序,我没有休眠会话等。

【问题讨论】:

我无法理解您的代码格式。 【参考方案1】:

您可以使用的替代方案很少:

使用级联持久性:

@OneToMany(fetch=FetchType.LAZY, mappedBy="user", cascade = CascadeType.PERSIST)
List<OAuthLogin> oauthLogins;

在你的 Servlet 中做:

User user = new User(name);
user.setEmail(email);
OAuthLogin fbLogin = new OAuthLogin(user, OAuthProvider.FACEBOOK, "1501791394");      
user.getOauthLogins().add(fbLogin) // this is enough assuming uni-directional association
userDAO.persist(user);
List<OAuthLogin> oauthLogins = user.getOauthLogins();

这应该可以,而且您有一个事务和更少的 JDBC 调用。

这对于调用特定 Servlet 方法的特定用例很有帮助。

EJB 中的预取集合

public User findById(int id, boolean prefetch) 
    User entity = em.find(User.class, id);
    if (prefetch) 
        // Will trigger 1 or size JDBC calls depending on your fetching strategy
        entity.getOauthLogins().size() 
    
    return entity;

或者,Override fetch mode using a criteria

这对于您希望使用User 获取OAuthLogin 集合同时保留FetchType.LAZY 并避免仅针对该特定集合的LazyInitializationException 的每种情况都有帮助。

在视图过滤器中使用 Open Entity Manager

只要谷歌一下,你会发现很多例子

这基本上可以防止LazyInitializationException,每个延迟获取的关联,每个跨范围的实体应用程序

PS:

    如果不使用 Spring,为什么要使用 @Transactional(默认情况下甚至不适用于 HttpServlet) 它曾为 WebLogic 工作过,可能使用某种量身定制的解决方案

【讨论】:

谢谢,我马上试试。广告 1) 它是 JPA 1.2 中的 javax.transaction.Transactional。我试图明确指定它,但它没有帮助。 感谢您的更新。顺便说一句,注释是 JTA 规范的一部分,适用于 CDI 托管 bean 我尝试了第一种方法,用户被正确持久化。然后我搜索了这个实体,得到了列表,但是同样的异常又出现了> User user2 = userDAO.findById(user.getId()); > oauthLogins = user2.getOauthLogins(); 这是真的,它只适用于您保存父实体并以相同的“笔划”迭代子实体的情况。但是,当您手头有user 时,您不必再次访问数据库并获取user2。这两个对象代表同一个数据库行(User equals 方法应该为user.equsls(user2) 返回 true) 如果您从另一个 EJB 业务方法调用您的 DAO 方法,那么您已经在处理事务并且两个 EJB 的持久性上下文相同,因此您不必将 prefetch 设置为 true 或任何. EJB 业务方法使用TransactionAttributeType(REQUIRED) 隐式调用,因此在这种情况下,默认情况下两者都参与同一个事务【参考方案2】:

LazyInitializationException 表示您在关联会话关闭后访问惰性集合。

由于您的代码看起来不错,您的问题可能与此问题LazyInitializationException in JPA and Hibernate中的问题相同

您能否验证您的交易是否在方法开始时打开?

【讨论】:

【参考方案3】:

要在JPA中初始化laze,你需要调用一个jar库并启动它,如果是maven或者manual的话 例如,如果你需要懒惰,请在 maven 中使用 jar jar de.empulse.eclipselink More info http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Performance/Weaving/Static_Weaving#Use_the_Maven_plugin

【讨论】:

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

Jpa/Hibernate 字节码增强:字段延迟加载

JPA 选择,啥应该延迟加载,当不需要时

使用 EJB + JPA + Jersey 进行延迟加载

远程案例中的延迟/急切加载策略(JPA)

JPA:哪些实现支持延迟加载外部事务?

Spring JPA 延迟加载 - 无法初始化代理