【问题标题】:Join fetch in JPA sometimes causes LazyInitializationException在 JPA 中加入 fetch 有时会导致 LazyInitializationException
【发布时间】:2017-01-17 14:22:21
【问题描述】:

我在我的应用程序中遇到了 JPA 的这个问题。我有一个父实体 Artist 与其他实体一对一/多蚀刻关系,我都设置为延迟获取。我使用连接查询来获取这些实体。这一切似乎都很好,但有时我会得到一个 LazyInitializationException。我在后端使用带有 JPA 的无状态 EJB,在 Web 层使用 Spring MVC。方法如下:

public Artist getArtistWithChildren(long id, boolean isFetchReviews, boolean isFetchRequests, boolean isFetchGigs, boolean isFetchVenues) {
    StringBuilder sql = new StringBuilder();
    sql.append("select a from Artist a join fetch a.members mrs");
    if(isFetchReviews) {
        sql.append(" left join a.reviews rvs");
    } if(isFetchRequests) {
        sql.append(" left join a.requests rqs");
    } if(isFetchGigs) {
        sql.append(" left join a.gigs gs");
    } if(isFetchVenues) {
        sql.append(" left join a.venues vs");
    }

    sql.append(" where a.id=:id");

    TypedQuery<Artist> query = em.createQuery(sql.toString(), Artist.class);
    query.setParameter("id", id);
    query.setMaxResults(1);
    List<Artist> resultList = query.getResultList();
    return resultList.get(0);
}

这里是实体类 Artist

@Entity
@Table(name="ARTIST")
public class Artist extends DescribingEntity {

private static final long serialVersionUID = -7264327449601568983L;

@ManyToMany(mappedBy="artists", targetEntity=Member.class, fetch=FetchType.LAZY, cascade={MERGE, REFRESH})
private List<Member> members;

@OneToMany(mappedBy="artist", targetEntity=VenueReview.class, fetch=FetchType.LAZY, cascade={MERGE, REFRESH, REMOVE})
private List<VenueReview> reviews;

@OneToMany(mappedBy="artist", targetEntity=Gig.class, fetch=FetchType.LAZY, cascade={MERGE, REFRESH, REMOVE})
private List<Gig> gigs;

@OneToMany(mappedBy="artist", targetEntity=GigRequest.class, fetch=FetchType.LAZY, cascade={MERGE, REFRESH, REMOVE})
private List<GigRequest> requests;

@ManyToMany(cascade={MERGE, REFRESH}, fetch=FetchType.LAZY)
@JoinTable(name="VENUE_ARTIST_REL", 
    joinColumns=@JoinColumn(name="ARTIST_ID", referencedColumnName="ARTIST_ID"), 
    inverseJoinColumns=@JoinColumn(name="VENUE_ID", referencedColumnName="VENUE_ID"))
private List<Venue> venues;

getters and setters...

然后我进入调试模式以找出问题所在,并且当我逐步执行该方法时,它不会返回错误并且不会引发异常。会不会是集合返回得太快,以至于数据库中的所有数据都没有时间正确填充?我是 JPA 的新手,所以请让我知道我做错了什么。这是一个例子。我在这里使用左连接,因为我不需要得到结果,但我确实需要一个实例化的集合。

我已经使用 Arquillian 测试过同样的事情并且没有错误,对于场景相同的其他实体,我也有类似的方法,只是运行它会导致错误,而逐步调试则不会。

在子集合上使用 Hibernate.initialize(Object o) 效果很好,但据我所知,这不是一件好事,因为我必须为每个孩子进行数据库查询(如果我错了,请纠正我)。

我在堆栈跟踪中添加了一个 pastebin 链接

Stack Trace

【问题讨论】:

    标签: hibernate spring-mvc ejb jpa-2.0 lazy-initialization


    【解决方案1】:

    通常,当您延迟加载集合并尝试访问该集合而没有包含您尝试访问延迟集合的上下文(本质上是方法)的 Session 时,会发生 LazyInitializationException。如果您可以发布错误堆栈跟踪以准确解决您的问题,这将更有帮助

    【讨论】:

    • 我将堆栈跟踪添加为链接。
    • 没有会话,也不是没有事务。例如。分离实体
    • 我的错,是的,没有会话。感谢您纠正我。
    • 我可以看到以下消息:“无法初始化代理 - 无会话”。正如我之前提到的,这个问题是因为你试图访问延迟加载的集合,但是加载集合的 Session 已经关闭。此外,我可以从堆栈跟踪中看到,您正在使用 Spring 作为您的应用程序框架。如果是这种情况,请尝试在您访问延迟加载集合的方法上使用 @Transactional 注释。
    • 并非如此。如果我正确理解了您的问题,那么您有 JSP 视图,您将从 EJB 层获得的对象传递到该视图中,这就是访问您的惰性集合的时间点。如果是,我会说,尝试在早期阶段访问它(绑定在会话上下文中的方法),遍历您的集合,并将其设置为您的 ModelAndView 返回的不同对象(可能是 DTO) JSP 视图。注意事项:如果您打算使用 OSIV,则可能会面临 hibernate 的 N+1 问题,这可能会增加视图加载时间。
    【解决方案2】:

    原因解释如下:

    问题

    典型(web-)应用程序中的一个常见问题是视图的渲染,在动作的主要逻辑完成之后,因此,Hibernate Session 已经关闭并且数据库事务已经结束。如果您访问已在 JSP(或任何其他视图呈现机制)内的 Session 中加载的分离对象,您可能会遇到未加载的集合或未初始化的代理。您得到的异常是: LazyInitializationException: Session has been closed(或非常相似的消息)。当然,这是意料之中的事情,毕竟您已经结束了您的工作单元。

    一个解决方案是调用 Open Session in View 模式。对于基于 Spring 的应用程序,您可以使用 org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptororg.springframework.orm.hibernate5.support.OpenSessionInViewFilter,但实际上不能在一个项目中同时使用这两种方法。

    但出于某种原因,它被视为反模式:

    ** 参考:**

    Open Session in View

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-22
      • 2010-12-24
      • 1970-01-01
      • 2015-07-31
      • 1970-01-01
      • 1970-01-01
      • 2020-09-22
      • 2014-08-08
      相关资源
      最近更新 更多