如何解决“惰性初始化角色集合失败”Hibernate异常

我有这样一个问题:

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

699314 次浏览

如果你知道你想在每次检索Topic时看到所有的__abc0,那么将comments的字段映射更改为:

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

默认情况下,集合是惰性加载的,如果你想了解更多,可以看看

你的列表是惰性加载,所以列表没有加载。 仅仅通过电话进入名单是不够的。 在Hibernate中使用。初始化以初始化列表。 如果不起作用,运行列表元素并调用Hibernate。初始化每一个。 这需要在从事务范围返回之前进行。 查看 post.
搜索-

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

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

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

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

(1)使用Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2)使用JOIN FETCH

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

3)使用OpenSessionInViewFilter

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

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

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

更详细的描述在这里和这挽救了我的一天。

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

有两种解决方案。

  1. 不要使用惰性加载。

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

  2. 使用延迟加载。

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

    并在web.xml

    中添加OpenSessionInViewFilter filter

详细信息见my 帖子

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

@PersistenceContext(type = PersistenceContextType.EXTENDED)

问题的根源:

默认情况下,hibernate惰性加载集合(关系),这意味着无论何时你在代码中使用collection(这里是comments字段) 在Topic类中) hibernate从数据库中获取,现在的问题是,您正在获得控制器中的集合(其中 JPA会话已关闭)。这是导致异常的代码行 (你正在加载comments集合):

    Collection<Comment> commentList = topicById.getComments();
你正在你的控制器(JPA session已经结束的地方)中获得"comments"集合(topic.getComments()),这会导致异常。如果你有 在你的jsp文件中的comments集合(而不是在你的控制器中获取它):

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

出于同样的原因,仍然会出现相同的异常。

解决问题:

因为在一个Entity类中只能有两个带有FetchType.Eager(急切获取的集合)的集合,并且因为延迟加载更多 比急切加载有效,我认为这种解决问题的方法比仅仅将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>
这段代码的作用是它将增加你的JPA session的长度,或者如文档所说,它被使用"to allow for lazy loading in web views despite the original transactions already being completed."所以 这样,JPA会话打开的时间会更长一点 您可以在JSP文件和控制器类中惰性加载集合

如果你试图在一个实体和一个集合或java对象的列表(例如长类型)之间有一个关系,它会像这样:

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

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

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

我该做的都做了

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

为了解决这个问题,在我的例子中,它只是少了这一行

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

在应用程序上下文文件。

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

希望这个答案能帮助到一些人

控制器上缺少@Transactional注释

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}
我知道这是个老问题,但我想帮忙。 您可以将事务注释放在所需的服务方法上,在本例中,findTopicByID(id)应该具有

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

关于这个注释的更多信息可以找到在这里

关于其他解决方案:

fetch = FetchType.EAGER

这不是一个好的做法,只有在必要时才应该使用。

Hibernate.initialize(topics.getComments());

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

希望能有所帮助

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

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

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

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

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

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

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

可能的解决方式:

  1. 所有这些逻辑都在服务层吗,(带有@Transactional),不在控制器中。应该有正确的地方来做这件事,它是应用程序逻辑的一部分,而不是在控制器(在这种情况下,一个加载模型的接口)。服务层中的所有操作都应该是事务性的。 例如:移动这一行到TopicService。findTopicByID方法:< / p >

    收集commentList = topicById.getComments();

    . getcomments ()
  2. 用'eager'代替'lazy'。现在你没有使用lazy ..这不是一个真正的解决方案,如果你想使用lazy,它就像一个临时的(非常临时的)解决方案。

  3. 在控制器中使用@Transactional。这里不应该使用它,你把服务层和表示层混在一起了,这不是一个好的设计。
  4. 使用OpenSessionInViewFilter,许多缺点报告,可能不稳定。

一般来说,最佳解是1。

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

topicById.getComments();

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

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

在我的情况下,我有映射b/w AB

A已经

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

DAO层中,如果你还没有用获取类型-渴望注释映射,那么方法需要用@Transactional进行注释

你的模型类Topic中的集合comments是惰性加载的,如果你没有专门用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. fetch = FetchType.EAGER注释comments

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
    
  2. If you still would like comments to be lazily loaded, use Hibernate's stateful sessions, so that you'll be able to fetch comments later on demand.

嗨,所有的帖子都发得很晚,希望能帮助到其他人。 提前感谢@GMK的文章Hibernate.initialize(对象)

当懒= " true "

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

现在,如果我在关闭会话后访问'set',它会抛出异常。

我的解决方案:

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

现在我可以在关闭Hibernate会话后访问“set”。

通过使用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,因为您显式地请求它。

最好的解决方案之一是在应用程序中添加以下内容。属性文件: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true < / >强

还有另一种方法,你可以使用TransactionTemplate来封装惰性取回。 像< / p >
Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

你应该为fetch = FetchType.LAZY准备两个东西。

@Transactional

而且

Hibernate.initialize(topicById.getComments());

导致这个问题的原因是,当数据库的“连接”关闭时,代码正在访问一个惰性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;
}

不是最好的解决方案,但对于那些面临LazyInitializationException的人,特别是在Serialization上,这将有所帮助。在这里,你将检查惰性初始化的属性,并将null设置为这些属性。为此,创建以下类

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

在你的实体类中,你有一个惰性初始化的属性,添加如下所示的方法。在这个方法中添加所有延迟加载属性。

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

在所有加载数据的地方之后调用这个checkLazyIntialzation()方法。

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

为了摆脱惰性初始化异常,在操作分离对象时不应该调用惰性收集。

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

对于某些特定的情况,你可以使用@EntityGrpaph注释,它允许你加载eager,即使你的实体中有fetchType=lazy

对于这个惰性初始化问题有多种解决方案

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

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

3)另外,在控制器层请尽量使用DTO对象而不是实体。在你的例子中,会话在控制器层被关闭。所以最好在服务层将实体转换为DTO。

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

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

如果你没有显式地定义你的查询,所以你可以指定一个join fetch,然后使用@NamedEntityGraph@EntityGraph 你可以重写 FetchType.LAZY (@OneToMany关联默认使用LAZY) 在运行时,并与Topic 只有在需要的时候同时加载注释。这意味着您将注释的加载限制在那些真正需要注释的方法(查询)上。一个实体图JPA 定义了它:

实体图可以与find方法一起使用,也可以作为查询提示

. override或augment FetchType语义

您可以基于JPA示例在这里使用它。或者,如果你使用Spring Data JPA,那么你可以基于例子Spring提供来使用它。

在第二次执行生成JWT令牌的方法后,我得到了这个错误。

.forEachOrdered .stream user.getUsersRole () () ((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()));


在我的例子中,@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()));


在我的Spring-Boot项目中,application.properties中将spring.jpa.open-in-view设置为false。将其设置为true就解决了这个问题。