我有这样一个问题:

org.hibernate.LazyInitializationException:惰性初始化role: 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;
    }

}

调用model的控制器如下所示:

@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 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循环的行中


当前回答

为了延迟加载集合,必须有一个活动会话。在web应用中,有两种方法可以做到这一点。您可以使用Open Session In View模式,其中使用拦截器在请求开始时打开会话,并在结束时关闭会话。风险在于你必须有可靠的异常处理,否则你可能会绑定所有的会话,你的应用程序可能会挂起。

The other way to handle this is to collect all the data you need in your controller, close your session, and then stuff the data into your model. I personally prefer this approach, as it seems a little closer to the spirit of the MVC pattern. Also if you get an error from the database this way you can handle it a lot better than if it happens in your view renderer. Your friend in this scenario is Hibernate.initialize(myTopic.getComments()). You will also have to reattach the object to the session, since you're creating a new transaction with every request. Use session.lock(myTopic,LockMode.NONE) for that.

其他回答

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

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

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

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

对于那些使用Criteria的人,我发现

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

我该做的都做了

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

此问题是由于在关闭hibernate会话的情况下访问属性造成的。控制器中没有hibernate事务。

可能的解决方式:

Do all this logic, in the service layer, (with the @Transactional), not in the controller. There should be the right place to do this, it is part of the logic of the app, not in the controller (in this case, an interface to load the model). All the operations in the service layer should be transactional. i.e.: Move this line to the TopicService.findTopicByID method: Collection commentList = topicById.getComments(); Use 'eager' instead of 'lazy'. Now you are not using 'lazy' .. it is not a real solution, if you want to use lazy, works like a temporary (very temporary) workaround. use @Transactional in the Controller. It should not be used here, you are mixing service layer with presentation, it is not a good design. use OpenSessionInViewFilter, many disadvantages reported, possible instability.

一般来说,最佳解是1。

原因是你试图在关闭服务内的会话后在你的控制器上获得commentList。

topicById.getComments();

只有当您的休眠会话处于活动状态时,上面才会加载commentList,我猜您在服务中关闭了休眠会话。

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

通过使用hibernate @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()执行另一个查询来获取sales,因为您显式地请求了它。