org.hibernate.LazyInitializationException: could not initialize proxy - no Session,再次出现

11

Foo看起来有这个:

@ManyToMany
private Set<User> favouritedBy;

当用户拥有此时:

@ManyToMany(mappedBy = "favouritedBy")
private Set<Foo> favourites  = new HashSet<Foo>();
public Set<Foo> getFavourites() {
  return favourite;
}

fooService拥有这个功能,使用事务方法在会话打开时访问延迟加载的集合:

@Transactional(readOnly = true)
public Set<Foo> getFavourites(User user) {
user = dao.get(User.class, user.getId()); //the dao gets a session
Set<Foo> favourites = user.getFavourites();//but the session is not here and the exception is thrown?
return  favourties;
}

编辑 此方法修复了问题,无需使用条件:

Set<Foo> favourites = new HashSet<Foo>(user.getFavourites());

并且这个标准修复了它

Session session = sessionFactory.getCurrentSession();
final Criteria crit = session.createCriteria(Foo.class);
crit.setFetchMode("favourites", FetchMode.JOIN);
crit.add(Property.forName("id").eq(id));
return (Foo) crit.uniqueResult();

你确定在Spring上下文中设置了“transactionManager”并定义了“tx:annotation-driven”吗? - hoaz
是的,在其他地方一切都正常运作... - NimChimpsky
你能在这里发布堆栈跟踪吗?我们将检查是否存在事务处理代码。 - hoaz
4个回答

12
默认情况下,ManyToMany 中的 FetchTypeLAZY,而 hibernate 文档中关于处理延迟加载关联的部分明确指出这种访问方式是错误的。您只能在会话仍然处于打开状态时与延迟关联对象进行交互。该文档还提供了访问对象的这些延迟关联成员的替代方法。我们更倾向于在使用标准来查询时将获取模式设置为JOIN 在我们的应用程序中。
Set<Foo> favourites = user.getFavourites();
上述语句实际上并不返回包含所有Foo对象的集合,它只是一个代理。只有像favorites.iterator()等访问集合中元素时才会获取实际的Foo对象。这个操作显然是在getFavorites()方法之外发生的。但是getFavorites()方法上的@Transactional注解表示会在该方法结束时关闭会话。
因此,当调用收藏夹集合上的方法时,会话已经关闭,因此出现异常。
为了解决这个问题,您应该使用Criteria对象检索用户,并将获取类型指定为JOIN,以便在返回的用户对象中填充Foo对象。

当您的惰性关联集被复制以供“getFavorites()”方法的调用者使用时,会话已经关闭,因为事务的范围在“getFavorites()”方法结束时结束。 - Vikdor
请查看更新,我尝试解释发生了什么。 - Vikdor
1
我可以直接执行 "Set<Foo> favourites = new HashSet<Foo>(user.getFavourites());"。 - NimChimpsky
强制在会话边界内遍历集合,太好了! - Vikdor
非常感谢!您的信息帮助我弄清了我列定义中唯一缺失的部分是@Fetch(FetchMode.JOIN),位于@ManyToMany(fetch = FetchType.LAZY)@JoinTable之上。 - user11814208
显示剩余7条评论

9
有两种解决方案。
  1. Don't use lazy load.

    Set lazy=false in XML or Set @OneToMany(fetch = FetchType.EAGER) In annotation.

  2. Use lazy load.

    Set lazy=true in XML or Set @OneToMany(fetch = FetchType.LAZY) In annotation.

    and add filter in your web.xml

     <listener>
         ...
     </listener>
     <filter>
         <filter-name>hibernateFilter</filter-name>
         <filter-class>
             org.springframework.orm.hibernate4.support.OpenSessionInViewFilter
         </filter-class>
         <init-param>
             <param-name>sessionFactoryBeanName</param-name>
             <param-value>mySessionFactory</param-value> 
         </init-param>
     </filter>
     <filter-mapping>
         <filter-name>hibernateFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
     <servlet>
         ...
     </servlet>
    

<param-value>mySessionFactory</param-value> 则是你在 applicationContext.xml 中定义的 sessionFacory bean 的名称。


1
本文讨论了以下提出的解决方案:https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/ - olivmir
OpenInView 是一个不好的解决方案,应该在任何其他可用解决方案之前使用。在使用这种反模式之前,有很多更好的方法可以修复它。 - user11814208

2
是的,对象应该在事务上下文中访问,否则操作将抛出LazyInitializationException异常。

0

如果您在使用任何@...Many...关系与Fetch类型“Lazy”时遇到了LazyInitializationException - 这意味着OpenInView已关闭,这是好的。

为避免LazyInitializationException和打开OIV(这会使Hibernate会话保持打开时间比大多数情况下需要的时间更长) - 请确保在发出列上指定了@Fetch(FetchMode.JOIN)。

例如:

@ManyToMany(fetch = FetchType.LAZY)
private Set<Kek> keks;

之后:

@Fetch(FetchMode.JOIN)
@ManyToMany(fetch = FetchType.LAZY)
private Set<Kek> keks;

通过这种方式,您将强制使用Join Fetch类型,它会(简单地说)提供正确的查询,其中包含所需的链接实体已连接,而不强制您使用Eager fetch。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接