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

Eug*_*ene 340 java spring jsp hibernate 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

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

@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);

     }

}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

查看jsp时会出现例外情况.在c:forEach循环的行中

dar*_*man 197

如果您知道Comment每次检索时都要查看所有内容,Topic那么请将字段映射更改为comments:

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

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

  • 这可以用作解决方法,但不是问题的实际解决方案.如果我们需要懒惰地拿走怎么办? (226认同)
  • 这是堆栈溢出时到处弹出的答案类型.简而言之,解决问题和误解.对于未来的读者,帮自己一个忙,了解究竟什么是懒惰和急切的,并了解其后果. (86认同)
  • 对不起,但我想使用延迟加载.所以,我已经将'LinkedHashSet'类型更改为'PersistentList'.例外仍然存在 (29认同)
  • 问题清楚地提到这将是懒惰的负载. (19认同)
  • 但是如果我们想要懒惰,那么这个解决方案将不起作用,大多数情况下我们只想懒惰. (13认同)
  • @darrengorman当我开始JPA时,我发布了一个关于OP的问题的问题.我收到了你给出的同样的答复.很快,当我用数十万行进行一些测试时,猜猜发生了什么?我认为这是误导性的,因为它为一个大多数初学者将面临的问题提供了太简单的答案,很快他们将整个数据库加载到内存中,如果他们不小心(他们不会,因为他们不会要注意它):). (12认同)
  • @prashantthakre显然如果你想要延迟加载它将无法工作,这个答案的重点是它热切地加载对象 (2认同)
  • @Ced 答案以何种方式误导?它清楚地指出,这只是一个解决方案*如果*每次都加载集合。最初 OP 表示他们想懒惰地获取,但后来改变了主意并接受了答案。答案甚至链接到对两种策略之间差异的简明解释。我提供了一个可能的解决方案,但从未声称这是唯一的解决方案。 (2认同)
  • 这实际上并不是问题的解决方案,它只是更改为使用急切加载,这在许多情况下实际上可能并不合适。 (2认同)
  • 如何通过将获取类型更改为EAGER来解决此问题 (2认同)
  • 这是最热门/接受的答案吗?显然,人们来这里是为了找出如何解决延迟加载异常。解决方案不是切换到使用急切加载...... (2认同)
  • 这不应该被接受,因为它具有误导性。建议在不进一步思考的情况下启用 EAGER 加载可能会在将来产生其他大问题。是一个糟糕的解决方案。 (2认同)
  • @Rafael 在软件开发中做任何事情而没有进一步思考可能会在未来产生问题。在我回答这个问题 7.5 年后,你的评论没有增加任何价值。 (2认同)

Bor*_*ris 173

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

(1)使用Hibernate.initialize

Hibernate.initialize(topics.getComments());
Run Code Online (Sandbox Code Playgroud)

(2)使用JOIN FETCH

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

(3)使用OpenSessionInViewFilter

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

  • 似乎Hibernate.initialize不能与EntityManager一起使用 (6认同)
  • 这应该是正确的答案.例如,在我的工作项目中,我们明确地不应该使用EAGER获取.它会在这个特定的系统中引起问题. (6认同)
  • (1)完美地为我工作.我的情况:Hibernate.initialize(registry.getVehicle().getOwner().getPerson().getAddress()); (4认同)
  • 看起来很有吸引力,但缺乏在另一种情况下实施的文档...您能否提供一些有关如何实施此解决方案的更多链接或解释? (2认同)

gan*_*alf 51

问题的根源:

默认情况下,hibernate懒洋洋地加载集合(关系),这意味着当你collection在代码中使用它时(这里commentsTopic类中的字段),hibernate从数据库中获取,现在的问题是你在控制器中获取集合(JPA会话中)已关闭).这是导致异常的代码行(您加载comments集合的地方):

    Collection<Comment> commentList = topicById.getComments();
Run Code Online (Sandbox Code Playgroud)

您正在控制器中获取"comments"集合(topic.getComments())(JPA session已结束)并导致异常.另外,如果您comments在jsp文件中有这样的集合(而不是在控制器中获取它):

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>
Run Code Online (Sandbox Code Playgroud)

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

解决问题:

因为你只能FetchType.Eager在Entity类中只有两个集合(eagerly fetched collection),并且因为延迟加载比急切加载更有效,我认为这种解决问题的方法比仅仅更改FetchType为eager 更好:

如果你想初始化集合延迟,并且还可以使这个工作,最好将这段代码添加到你的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>
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么JPS会话关闭?如何让它不被关闭?如何进行懒人收藏? (5认同)

sar*_*pex 47

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

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
Run Code Online (Sandbox Code Playgroud)

有关此注释的更多信息,请点击此处

关于其他解决方案:

fetch = FetchType.EAGER 
Run Code Online (Sandbox Code Playgroud)

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

Hibernate.initialize(topics.getComments());
Run Code Online (Sandbox Code Playgroud)

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

希望能帮助到你

  • 是的,但我认为可以理解,你可以真正改变传播参数 (4认同)
  • @Transactional注释对我有用,但请注意,Propagation.REQUIRED是默认值,至少在Spring Boot 1.4.2(Spring 4.3)中是这样. (3认同)

小智 29

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

有两种解决方案.

  1. 不要使用延迟加载.

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

  2. 使用延迟加载.

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

    并添加OpenSessionInViewFilter filter你的web.xml

细节见我的帖子.

  • ...但这两种解决方案都不好。建议使用 EAGER 会产生巨大的问题。使用 OpenSessionInViewFilter 是一种反模式。 (3认同)

小智 21

@Controller
@RequestMapping(value = "/topic")
@Transactional
Run Code Online (Sandbox Code Playgroud)

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

  • @Rafael,我明白你的意思 - 在服务层“最终确定”生成的 DTO 并将其传递到表示层。我猜测,如果他使用 MapStruct,他会将其移至服务层并使用它解析 DTO。一个健全的解决方案。 (3认同)
  • 将 @Transactional 广告到控制器是不好的做法。 (2认同)
  • @JeSuisAlrick 整个网站都是基于专业建议。我的专业建议是避免提出的解决方案并将其视为一种不好的做法,并且我对原因给出了非常明确的解释。当然,任何人都可以不同意这一点。如果您不想接受该解释,因为它不是来自 Spring,那是您的决定。其他人可能同意也可能不同意你的观点。但我必须投反对票,因为我真的认为这是一个误导性的答案。 (2认同)

abe*_*tan 21

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

可能的解决方案:

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

    集合commentList = topicById.getComments();

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

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

一般来说,最好的解决方案是1.

  • Eager的访存类型假定休眠将在第一个查询中拉出所有数据,而不是正确放置的所有位置 (2认同)

GMK*_*GMK 18

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

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


use*_*932 13

在第二次执行生成 JWT 令牌的方法后,我收到此错误。

该行user.getUsersRole().stream().forEachOrdered((ur) -> Roles.add(ur.getRoleId())); 产生了错误。

// MyUserDetails.java

@Service
public class MyUserDetails implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String email) {

    /* ERROR
    /* org.hibernate.LazyInitializationException: failed to 
    /* lazily initialize a collection of role: 
    /* com.organizator.backend.model.User.usersRole, 
    /* could not initialize proxy - no Session */
    user.getUsersRole().stream().forEachOrdered((ur) ->
           roles.add(ur.getRoleId()));

Run Code Online (Sandbox Code Playgroud)

就我而言,@Transactional注释解决了这个问题,

// MyUserDetails.java

import org.springframework.transaction.annotation.Transactional;

@Service
public class MyUserDetails implements UserDetailsService {

  @Override
  @Transactional // <-- added
  public UserDetails loadUserByUsername(String email) {

    /* No Error */
    user.getUsersRole().stream().forEachOrdered((ur) ->
           roles.add(ur.getRoleId()));

Run Code Online (Sandbox Code Playgroud)


Vla*_*cea 12

正如我在本文中所解释的,处理的最佳方法LazyInitializationException是在查询时获取它,如下所示:

select t
from Topic t
left join fetch t.comments
Run Code Online (Sandbox Code Playgroud)

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

因此,请确保FetchType.LAZY在查询时或在用于辅助集合的原始@Transactional范围内初始化关联Hibernate.initialize

  • Vlad 您对在由 Spring 生成的存储库的 findById() 方法获取的实体中使用延迟初始化集合有什么建议吗?我不是在写查询,交易不在我的代码中。 (2认同)

小智 11

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

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;
Run Code Online (Sandbox Code Playgroud)

  • 在很多情况下,您确实不想这样做。您在这里失去了延迟加载的所有好处 (2认同)

小智 9

我发现,宣布@PersistenceContextEXTENDED也解决了这个问题:

@PersistenceContext(type = PersistenceContextType.EXTENDED)
Run Code Online (Sandbox Code Playgroud)

  • 您好,请小心此类更改。TRANSACTION 作用域持久化上下文的创建是惰性的,这正是 OP 的意图。所以问题是你是否想成为无国籍人。此设置取决于系统的用途,不应该太急于更改。如果你明白我的意思。阅读此处http://stackoverflow.com/questions/2547817/what-is-the-difference- Between-transaction-scoped-persistence-context-and-extend (3认同)

小智 9

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

@Transactional
Run Code Online (Sandbox Code Playgroud)

Hibernate.initialize(topicById.getComments());
Run Code Online (Sandbox Code Playgroud)


sre*_*tta 8

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

  • 你能告诉OP它到底做了什么、有什么副作用、性能影响吗? (2认同)
  • 在延迟加载的后面,每次延迟加载关联时都会派生一个新会话,因此会派生更多连接,并给连接池造成一定压力。如果您对连接数有限制,那么此属性可能不是正确使用的属性。 (2认同)
  • 对于某些人来说,它被认为是一种反模式 https://vladmihalcea.com/the-hibernate-enable_lazy_load_no_trans-anti-pattern/ (2认同)

小智 7

为了解决我的问题,它只是缺少这一行

<tx:annotation-driven transaction-manager="myTxManager" />
Run Code Online (Sandbox Code Playgroud)

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

@Transactional没有考虑方法上的注释。

希望答案能帮助某人


小智 7

导致该问题的原因是,当与数据库的“连接”关闭时,代码正在访问惰性 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;
    }
Run Code Online (Sandbox Code Playgroud)


小智 7

这个延迟初始化问题有多种解决方案 -

1) 将关联 Fetch 类型从 LAZY 更改为 EAGER 但这不是一个好的做法,因为这会降低性能。

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

3) 另外,请尝试在控制器层使用 DTO 对象而不是实体。在您的情况下,会话在控制器层关闭。所以最好在服务层将实体转换为 DTO。


Xco*_*der 6

缺少控制器上的@Transactional注释

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为事务管理属于业务逻辑所在的服务层. (13认同)

Avi*_*ano 5

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

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


tol*_*maz 5

这是我最近遇到的问题,我用它解决了

<f:attribute name="collectionType" value="java.util.ArrayList" />
Run Code Online (Sandbox Code Playgroud)

更详细的解释在这里,这节省了我的一天.


Bab*_*abi 5

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

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

如果您没有显式定义查询以便可以指定 a join fetch,则使用@NamedEntityGraph和您可以在运行时@EntityGraph 覆盖(关联默认情况下使用 LAZY )并仅在需要时同时加载注释。这意味着您将注释限制为仅加载那些真正需要的方法(查询)。JPA定义的实体图:FetchType.LAZY@OneToManyTopic

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

您可以根据此处的JPA 示例来使用它。或者,如果您使用 Spring Data JPA,那么您可以根据Spring 提供的示例来使用它。


归档时间:

查看次数:

462497 次

最近记录:

6 年,1 月 前