如何解决“无法延迟初始化角色集合”的Hibernate异常


363

我有这个问题:

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 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循环一致

Answers:


214

如果您知道Comment每次检索a 都想查看全部,Topic则将字段映射更改为comments

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

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


35
抱歉,但是我想使用延迟加载。因此,我更改了“ PersistentList”的“ LinkedHashSet”类型。仍然发生例外
Eugene

242
可以将其用作解决方法,但不能真正解决问题。如果我们需要懒洋洋地拿东西怎么办?
Dkyc 2014年

14
但是如果我们想偷懒,那么此解决方案将无法正常工作,并且在大多数情况下,我们只想偷懒。
令人沮丧的thakre 2015年

103
这是在堆栈溢出时到处弹出的答案类型。简而言之,就是解决了问题和误导。对于将来的读者,请帮自己一个忙,并了解什么是真正的懒惰和渴望获取的内容,并了解其后果。
2013年

13
@darrengorman当我开始JPA时,我围绕OP的内容发布了一个问题。我收到了与您相同的回复。足够快的时候,当我对成千上万的行进行测试时,您猜怎么了?我认为这是误导性的,因为它提供了对于大多数初学者将要面对的问题的简单答案,如果他们不小心的话,他们很快就会将整个数据库加载到内存中(而且他们不会,因为他们不会请注意):)。
2013年

182

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

(1)使用Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2)使用JOIN FETCH

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

(3)使用OpenSessionInViewFilter

LazyInitializationException通常在视图层中发生。如果使用Spring框架,则可以使用OpenSessionInViewFilter。但是,我不建议您这样做。如果使用不正确,可能会导致性能问题。


5
(1)完美地为我工作。我的情况:Hibernate.initialize(registry.getVehicle()。getOwner()。getPerson()。getAddress());
Leonel Sanches da Silva 2013年

6
似乎Hibernate.initialize无法与EntityManager一起使用
marionmaiden

8
这应该是正确的答案。例如,在我正在工作的项目中,我们明确不应该使用EAGER提取。这会导致该特定系统出现问题。
史蒂夫·沃特斯

似乎很有吸引力,但是缺乏在另一种情况下无法实现的文档...您能否提供更多有关如何实现此解决方案的链接或说明?
Pipo

58

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

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

有关此注释的更多信息,请参见此处

关于其他解决方案:

fetch = FetchType.EAGER 

这不是一个好习惯,请仅在必要时使用。

Hibernate.initialize(topics.getComments());

休眠初始化程序将您的类绑定到休眠技术。如果您想变得灵活,那不是一个好方法。

希望能帮助到你


3
@Transactional批注对我有用,但是请注意,Propagation.REQUIRED是默认设置,至少在Spring Boot 1.4.2(Spring 4.3)中。
ben3000 '16

4
是的,但是我想很清楚地表明您可以更改传播参数,这一点我们将不胜感激
sarbuLopex

这不是@Transactional春天的事吗?
坎帕

@Campa是的。如果要手动处理,则应将业务逻辑放入从实体管理器检索的事务中
sarbuLopex,

54

问题的根源:

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

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

您正在控制器(JPA session已结束的地方)中获得“评论”集合(topic.getComments()),这将导致异常。同样,如果您comments在这样的jsp文件中有该集合(而不是在您的控制器中得到它):

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

由于相同的原因,您仍然会有相同的例外。

解决问题:

因为您只能FetchType.Eager在Entity类中使用两个集合(急切地获取的集合),并且因为延迟加载比急切加载更有效,所以我认为这种解决问题的方法比只将急切加载更好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文件和控制器类中加载集合。


7
为什么JPS会话关闭?如何使其不被关闭?如何执行懒惰收集?
2015年

1
什么定义了每个实体两个FetchType.Eager集合的限制?
chrisinmtown

在Spring Boot中,您可以将'spring.jpa.open-in-view = true'添加到'application.properties'–
Askar,

28

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

有两种解决方案。

  1. 不要使用延迟加载。

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

  2. 使用延迟加载。

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

    并添加OpenSessionInViewFilter filter您的web.xml

详细信息,请参阅我的POST


1
...但是两种解决方案都不好。建议使用EAGER会产生巨大的问题。使用OpenSessionInViewFilter是一种反模式。
拉斐尔


22

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

可能的解决方案:

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

    集合commentList = topicById.getComments();

  2. 使用'eager'而不是'lazy'。现在您不使用'lazy'..这不是一个真正的解决方案,如果您想使用lazy,它的工作方式就像一个临时(非常临时)的解决方法。

  3. 在Controller中使用@Transactional。在这里不应该使用它,因为您正在将服务层与演示结合在一起,这不是一个好的设计。
  4. 使用OpenSessionInViewFilter,报告了许多缺点,可能会导致不稳定。

通常,最好的解决方案是1。


2
取的渴望假设Hibernate会被拉所有的数据在第一个查询,而不是所有的地方类型是正确的
ЖасуланБердибеков

您应该大写最好的解决方案是1 ...实际上是唯一的解决方案,因为所有其他解决方案都是反模式!
拉斐尔

19

为了延迟加载集合,必须有一个活动会话。在Web应用程序中,有两种方法可以执行此操作。您可以使用“ 在视图中打开会话”模式,在该模式中,您可以使用拦截器在请求开始时打开会话,并在请求结束时将其关闭。这样做的风险是您必须进行可靠的异常处理,否则您可能会束缚所有会话,并且应用程序可能会挂起。

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


15

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

select t
from Topic t
left join fetch t.comments

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

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


1
Vlad您对在由Spring生成的存储库的findById()方法获取的实体中使用延迟初始化的集合有什么建议吗?我没有编写查询,并且交易不在我的代码范围内。
chrisinmtown

查看 本文,以获取有关初始化惰性集合的更多详细信息。
弗拉德·米哈尔恰

您能否弄清楚“在原始@Transactional范围内”是什么意思,这对我来说还不清楚,因为我似乎在公开会议中遇到此错误(但不是正确的对话?)
Michiel Haisma,

在范围之内o最顶层的事务服务方法,也称为事务网关。签出TrassctionInterceptor堆栈跟踪中的那个。
Vlad Mihalcea

到目前为止最好的答案之一...这应该标记为正确的。顺便说一句...假设OSIV是反模式,那么如何在春季启动的最新版本中默认启用?...也许还不错?
拉斐尔

10

如果您试图在实体和Java对象的Collection或List之间建立关系(例如Long类型),则可能需要这样:

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

1
在许多情况下,您确实不想这样做。您在这里
失去

使用EAGER并不是专业的解决方案。
拉斐尔

9

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


1
您能告诉OP它到底有什么作用,任何副作用,对性能的影响吗?
PeS '18年

3
在延迟加载的后面,每次延迟加载关联时都会派生一个新会话,因此会派生更多连接,并给连接池造成一定压力。如果您对连接数有限制,则此属性可能不是正确的属性。
sreekmatta

2
对于一些人来说,被认为是一个反模式vladmihalcea.com/...
乌里支

7

我发现声明@PersistenceContextas EXTENDED也可以解决此问题:

@PersistenceContext(type = PersistenceContextType.EXTENDED)

1
嗨,请注意这些更改。TRANSACTION范围的持久性上下文的创建是懒惰的,这是OP的意图。所以问题是您是否想成为无国籍人。此设置取决于系统的用途,因此不应过于更改...。如果你明白我的意思。在这里阅读stackoverflow.com/questions/2547817/…–
kiedysktos

危险的。这不是正确的答案。还有其他一些更为准确和安全的方法。
拉斐尔

5

这是我最近遇到的问题,我通过使用解决了

<f:attribute name="collectionType" value="java.util.ArrayList" />

这里更详细的说明,这节省了我的时间。


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 

4

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

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

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

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

希望答案会帮助某人


4

控制器上的@Transactional注释丢失

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

17
我认为事务管理属于业务逻辑所在的服务层。
清醒的

事务注释不丢失。控制器不应具有此类注释。这些注释应处于服务级别。
拉斐尔

4

通过使用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()另一个事实来获取销售量的事实是因为您明确提出了要求。


4

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

@Transactional

Hibernate.initialize(topicById.getComments());

2

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

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

做了我需要做的一切。

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


2

就我而言,以下代码是一个问题:

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

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

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

1

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

topicById.getComments();

仅当您的休眠会话处于活动状态时,以上内容才会加载commentList,我猜您已在服务中关闭了它。

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


2
是的,这是问题陈述,您也应该在Answer
Sarz

1

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

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

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

解决方案可以是:

  1. 注释commentsfetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
  2. 如果您仍然希望延迟加载注释,请使用Hibernate的有状态会话,以便以后可以按需获取注释。


1

就我而言,我有黑白映射AB例如

A

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

在该DAO层中,如果您尚未使用“ 获取类型-渴望”来注释映射,则需要对方法进行注释@Transactional


1

这不是最好的解决方案,但是对于那些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();

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,我也可以访问“设置”。


0

执行此操作的另一种方法是,可以使用TransactionTemplate来包装懒惰的提取。喜欢

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

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;
    }

0

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

我认为,最好的方法是使用DTO,而不是实体。在这种情况下,您可以显式设置要使用的字段。像往常一样就足够了。无需担心jackson ObjectMapperhashCodeLombok生成的东西会隐式调用您的方法。

对于某些特定情况,您可以使用@EntityGrpaph注释,eager即使您fetchType=lazy在实体中也可以进行标注。


0

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

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

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

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


-11

我解决了使用列表而不是设置:

private List<Categories> children = new ArrayList<Categories>();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.