如何解决“未能懒散地初始化角色集合”的Hibernate异常

时间:2012-07-31 18:18:11

标签: java spring hibernate jsp spring-mvc

我有这个问题:

  

org.hibernate.LazyInitializationException:懒得初始化角色集合:mvc3.model.Topic.comments,没有关闭会话或会话

以下是模型:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

调用模型的控制器如下所示:

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

jsp-page看起来如下:

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

查看jsp时出现例外情况。在 c:forEach 循环

的行中

31 个答案:

答案 0 :(得分:177)

如果您知道每次检索Comment时都希望看到所有Topic,请将comments的字段映射更改为:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();

默认情况下,集合是延迟加载的,如果您想了解更多信息,请查看this

答案 1 :(得分:156)

根据我的经验,我有以下方法来解决着名的LazyInitializationException:

(1)使用Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2)使用JOIN FETCH

您可以在JPQL中使用JOIN FETCH语法显式提取子集合。这有点像EAGER的提取方式。

(3)使用OpenSessionInViewFilter

LazyInitializationException经常出现在视图层中。如果使用Spring框架,则可以使用OpenSessionInViewFilter。但是,我不建议你这样做。如果使用不当,可能会导致性能问题。

答案 2 :(得分:47)

问题的根源:

默认情况下,hibernate会延迟加载集合(关系),这意味着当您在代码中使用collection时(此处comments字段 在Topic班) hibernate从数据库中获取,现在的问题是你在控制器中获取集合(其中 JPA会话已关闭。)这是导致异常的代码行 (您要加载comments集合的地方):

    Collection<Comment> commentList = topicById.getComments();

你得到了#34;评论&#34;控制器中的集合(topic.getComments())(JPA session已结束)并导致异常。如果你有的话 你的jsp文件中的comments集合就像这样(而不是在控制器中获取它):

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>

出于同样的原因,你仍会有同样的例外。

解决问题:

因为在Entity类中只有两个带有FetchType.Eager(急切获取的集合)的集合,并且因为延迟加载更多 我认为这种解决问题的方法比将FetchType更改为渴望更好:

如果你想要初始化集合延迟,并使这项工作, 最好将此代码段添加到web.xml

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

此代码的作用是它会增加JPA session的长度,或者正如文档所说,它使用"to allow for lazy loading in web views despite the original transactions already being completed."所以 通过这种方式,JPA会议将开放一段时间,因此 你可以在jsp文件和控制器类中懒洋洋地加载集合。

答案 3 :(得分:41)

我知道这是一个老问题,但我想提供帮助。 您可以将事务注释放在您需要的服务方法上,在这种情况下,findTopicByID(id)应该具有

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)

可以找到有关此注释的更多信息here

关于其他解决方案:

fetch = FetchType.EAGER 

不是一个好习惯,只有在必要时才应该使用。

Hibernate.initialize(topics.getComments());

hibernate初始化程序将您的类绑定到hibernate技术。如果你的目标是灵活不是一个好的方法。

希望有所帮助

答案 4 :(得分:28)

原因是当您使用延迟加载时,会话将被关闭。

有两种解决方案。

  1. 不要使用延迟加载。

    在XML中设置lazy=false或在注释中设置@OneToMany(fetch = FetchType.EAGER)

  2. 使用延迟加载。

    在XML中设置lazy=true或在注释中设置@OneToMany(fetch = FetchType.LAZY)

    并在OpenSessionInViewFilter filter

  3. 中添加web.xml

    详情请参阅我的POST

答案 5 :(得分:18)

为了延迟加载集合,必须有一个活动会话。在Web应用程序中,有两种方法可以执行此操作。您可以使用Open Session In View模式,使用interceptor在请求开头打开会话,并在结束时关闭它。存在这样的风险:您必须具有可靠的异常处理,或者您可以绑定所有会话,并且您的应用可能会挂起。

处理此问题的另一种方法是收集控制器中所需的所有数据,关闭会话,然后将数据填入模型中。我个人更喜欢这种方法,因为它似乎更接近MVC模式的精神。此外,如果您从数据库中收到错误,则可以比在视图渲染器中更好地处理它。您在这种情况下的朋友是Hibernate.initialize(myTopic.getComments())。您还必须将对象重新附加到会话,因为您正在为每个请求创建一个新事务。使用session.lock(myTopic,LockMode.NONE)。

答案 6 :(得分:18)

问题是由关闭hibernate会话访问属性引起的。您在控制器中没有休眠事务。

可能的解决方案:

  1. 在服务层中执行所有这些逻辑(使用@Transactional),而不是在控制器中。应该有合适的位置,它是应用程序逻辑的一部分,而不是控制器(在这种情况下,是加载模型的接口)。服务层中的所有操作都应该是事务性的。 即:将此行移至TopicService.findTopicByID方法:

    收集commentList = topicById.getComments();

  2. 使用&#39;渴望&#39;而不是懒惰&#39; 。现在你没有使用&#39;懒惰&#39; ..它不是一个真正的解决方案,如果你想使用懒惰,就像一个临时(非常临时)的解决方法。

  3. 在Controller中使用@Transactional 。它不应该在这里使用,你将服务层与表示混合,它不是一个好的设计。
  4. 使用OpenSessionInViewFilter ,报告了许多缺点,可能不稳定。
  5. 通常,最佳解决方案是1。

答案 7 :(得分:17)

@Controller
@RequestMapping(value = "/topic")
@Transactional

我通过添加@Transactional解决了这个问题,我认为这可以使会话打开

答案 8 :(得分:11)

如果您尝试在实体和集合或Java对象列表之间建立关系(例如Long类型),它会像这样:

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;

答案 9 :(得分:9)

我发现将@PersistenceContext声明为EXTENDED也解决了这个问题:

@PersistenceContext(type = PersistenceContextType.EXTENDED)

答案 10 :(得分:6)

正如我在this article中所解释的那样,处理LazyInitializationException的最佳方法是在查询时获取它,例如:

select t
from Topic t
left join fetch t.comments

您应该始终避免使用以下反模式:

因此,请确保您的FetchType.LAZY关联在查询时或在原始@Transactional范围内使用Hibernate.initialize进行二级集合初始化。

答案 11 :(得分:5)

您的列表是延迟加载的,因此列表未加载。 打电话到列表是不够的。 在Hibernate.initialize中使用以初始化列表。 如果dosnt工作在list元素上运行并为每个元素调用Hibernate.initialize。 这需要在从事务范围返回之前。 看this帖子。
搜索 -

Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session 

答案 12 :(得分:5)

缺少

控制器上的@Transactional注释

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}

答案 13 :(得分:5)

这是我最近遇到的问题,我用

解决了这个问题
<f:attribute name="collectionType" value="java.util.ArrayList" />

更详细的解密here,这节省了我的一天。

答案 14 :(得分:4)

为了解决我的问题,它只是错过了这一行

<tx:annotation-driven transaction-manager="myTxManager" />

在应用程序上下文文件中。

未考虑方法的@Transactional注释。

希望答案能帮助某人

答案 15 :(得分:4)

此懒惰初始化问题有多种解决方案-

1)将关联提取类型从LAZY更改为EAGER,但这不是一个好习惯,因为这会降低性能。

2)在关联的对象上使用FetchType.LAZY,并在服务层方法中使用事务注释,以便会话将保持打开状态,并且当您调用topicById.getComments()时,子对象(comments)将被加载。

3)另外,请尝试使用DTO对象代替控制器层中的实体。在您的情况下,会话在控制器层关闭。因此最好在服务层将实体转换为DTO。

答案 16 :(得分:4)

最好的解决方案之一是在application.properties文件中添加以下内容: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true

答案 17 :(得分:3)

fetch = FetchType.LAZY应该有两件事。

@Transactional

Hibernate.initialize(topicById.getComments());

答案 18 :(得分:3)

通过使用休眠@Transactional注释,如果您从数据库中获得了具有延迟获取的属性的对象,则可以像这样通过获取这些属性来简单地获取它们:

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

在这里,在Hibernate代理管理的事务中,调用ticket.getSales()的事实是执行另一个查询以获取销售,因为您明确提出了要求。

答案 19 :(得分:2)

在我的情况下,代码是一个问题:

entityManager.detach(topicById);
topicById.getComments() // exception thrown

因为它与数据库分离,并且Hibernate在需要时不再从字段中检索列表。所以我在分离之前将其初始化:

Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm

答案 20 :(得分:2)

对于那些使用Criteria的人,我找到了

criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);

我做了我需要做的一切。

集合的初始提取模式设置为FetchMode.LAZY以提供性能,但是当我需要数据时,我只需添加该行并享受完全填充的对象。

答案 21 :(得分:1)

模型类comments中的集合Topic的加载是延迟的,如果您不专门用fetch = FetchType.EAGER对其进行注释,则这是默认行为。

您的findTopicByID服务很可能正在使用无状态的Hibernate会话。 A stateless session没有第一级缓存,即没有持久性上下文。稍后,当您尝试迭代comments时,Hibernate将引发异常。

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

解决方案可以是:

  1. comments注释fetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
    
  2. 如果您仍然希望延迟加载评论,请使用Hibernate's stateful sessions,以便以后可以按需获取评论。

答案 22 :(得分:1)

这是一个老问题,但以下信息可能会帮助人们寻找答案。

@VladMihalcea 的回答很有用。您不能依赖 FetchType.EAGER ,而是应该在需要时将评论加载到 Topic 实体中。

如果您没有明确定义您的查询以便指定 join fetch,那么使用 @NamedEntityGraph@EntityGraph 您可以覆盖{{1} }}(FetchType.LAZY 关联默认使用 LAZY)在运行时,并与 @OneToMany 同时加载注释,仅在需要时。这意味着您将注释加载到仅那些真正需要的方法(查询)。作为 JPA defines 的实体图:

<块引用>

实体图可以与 find 方法一起使用或作为查询提示使用 覆盖或增强 FetchType 语义。

您可以根据 JPA 示例 here 使用它。或者,如果您使用 Spring Data JPA,那么您可以基于示例 provided by Spring 使用它。

答案 23 :(得分:0)

大家好,我发帖很晚,希望对其他人有帮助, 在此先感谢@GMK Hibernate.initialize(object)

当Lazy =“ true”

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

现在,如果我在关闭会话后访问“设置”,则会引发异常。

我的解决方案:

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

现在即使关闭Hibernate Session,我也可以访问“设置”。

答案 24 :(得分:0)

在我的cae中,我有映射b / w A和B之类的

A有

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

在DAO图层中,如果您没有使用抓取类型 - 渴望 <注释该映射,则方法需要使用@Transactional进行注释/ p>

答案 25 :(得分:0)

原因是您在关闭服务中的会话后尝试在控制器上获取commentList。

topicById.getComments();

只有当您的休眠会话处于活动状态时,才会加载评论列表,我猜您已在服务中关闭。

因此,您必须在关闭会话之前获取commentList。

答案 26 :(得分:0)

还有另一种处理方法,您可以使用TransactionTemplate来包装懒惰的获取。 像

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

答案 27 :(得分:0)

引起此问题的原因是,当关闭与数据库的“连接”时,代码正在访问惰性JPA关系(持久性上下文是Hibernate / JPA的正确名称)。

在Spring Boot中解决此问题的一种简单方法是定义服务层并使用@Transactional批注。方法中的此注释创建一个事务,该事务传播到存储库层并保持打开持久性上下文,直到方法完成为止。如果您在事务方法内部访问集合,则Hibernate / JPA将从数据库中获取数据。

在您的情况下,您只需要在@Transactional中用findTopicByID(id)注释方法TopicService,并在该方法中强制获取集合(例如,通过询问其大小) ):

    @Transactional(readOnly = true)
    public Topic findTopicById(Long id) {
        Topic topic = TopicRepository.findById(id).orElse(null);
        topic.getComments().size();
        return topic;
    }

答案 28 :(得分:0)

这不是最好的解决方案,但是对于LazyInitializationException(尤其是Serialization)面临的人们来说,这将有所帮助。在这里,您将检查延迟初始化的属性,并将null设置为这些属性。为此,创建下面的类

public class RepositoryUtil {
    public static final boolean isCollectionInitialized(Collection<?> collection) {
        if (collection instanceof PersistentCollection)
            return ((PersistentCollection) collection).wasInitialized();
        else 
            return true;
    }   
}

在具有延迟初始化属性的Entity类内部,添加如下所示的方法。在此方法中添加所有延迟加载属性。

public void checkLazyIntialzation() {
    if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
        yourlazyproperty= null;
    }

在所有要加载数据的地方调用此checkLazyIntialzation()方法。

 YourEntity obj= entityManager.find(YourEntity.class,1L);
  obj.checkLazyIntialzation();

答案 29 :(得分:0)

要摆脱延迟初始化异常,在处理分离对象时不应调用延迟收集。

我认为,最好的方法是使用DTO,而不是实体。在这种情况下,您可以显式设置要使用的字段。像往常一样就足够了。不必担心Lombok生成的杰克逊hashCode@EntityGrpaph之类的东西会隐式调用您的方法。

在某些特定情况下,您可以使用eager批注,即使您的实体中有fetchType=lazy,也可以使 <input class="option" type="radio" value="{{optionsVal.id}}" id="opt_{{optionsVal.id}}" name="quizOption" (change)="radioFun( optionsVal.id)" /> 加载。

答案 30 :(得分:-9)

我解决了使用List而不是Set:

private List<Categories> children = new ArrayList<Categories>();