![](/img/trans.png)
[英]In Hibernate JPA how to transparently solve “ failed to lazily initialize a collection of role ” exception
[英]How to solve the “failed to lazily initialize a collection of role” Hibernate exception
我有这个问题:
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循环一致
如果您知道每次检索Topic
时都希望看到所有Comment
,则将comments
的字段映射更改为:
@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
默认情况下,集合是延迟加载的,如果您想了解更多信息,请查看此内容。
根据我的经验,我有以下方法来解决著名的 LazyInitializationException:
(1) 使用 Hibernate.initialize
Hibernate.initialize(topics.getComments());
(2) 使用 JOIN FETCH
您可以在 JPQL 中使用 JOIN FETCH 语法来显式提取子集合。 这有点像 EAGER fetching。
(3) 使用 OpenSessionInViewFilter
LazyInitializationException 经常发生在视图层。 如果你使用 Spring 框架,你可以使用 OpenSessionInViewFilter。 但是,我不建议您这样做。 如果使用不当,可能会导致性能问题。
我知道这是一个老问题,但我想帮忙。 您可以将事务注释放在您需要的服务方法上,在这种情况下 findTopicByID(id) 应该有
@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
可以在此处找到有关此注释的更多信息
关于其他解决方案:
fetch = FetchType.EAGER
不是一个好的做法,它应该只在必要时使用。
Hibernate.initialize(topics.getComments());
hibernate 初始化程序将您的类绑定到休眠技术。 如果您的目标是灵活,那不是一个好方法。
希望能帮助到你
默认情况下,hibernate 会延迟加载集合(关系),这意味着无论何时在代码中使用collection
(这里是Topic
类中的comments
字段),hibernate 从数据库中获取它,现在的问题是您在控制器中获取集合(其中JPA 会话已关闭)。这是导致异常的代码行(您正在加载comments
集合):
Collection<Comment> commentList = topicById.getComments();
您在控制器( JPA session
已结束)中获得“评论”集合(topic.getComments()),这会导致异常。 此外,如果您在 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 文件和控制器类中延迟加载集合。
@Controller
@RequestMapping(value = "/topic")
@Transactional
我通过添加@Transactional
解决了这个问题,我认为这可以使会话打开
处理LazyInitializationException
的最佳方法是在查询时加入 fetch ,如下所示:
select t
from Topic t
left join fetch t.comments
您应该始终避免以下反模式:
FetchType.EAGER
hibernate.enable_lazy_load_no_trans
休眠配置属性因此,请确保您的FetchType.LAZY
关联在查询时或在原始@Transactional
范围内使用Hibernate.initialize
进行辅助集合初始化。
该问题是由在休眠会话关闭的情况下访问属性引起的。 您在控制器中没有休眠事务。
可能的解决方案:
在服务层(使用@Transactional)中而不是在控制器中执行所有这些逻辑。 应该有合适的地方来做这件事,它是应用程序逻辑的一部分,而不是在控制器中(在这种情况下,是加载模型的接口)。 服务层中的所有操作都应该是事务性的。 即:将此行移至 TopicService.findTopicByID 方法:
集合commentList = topicById.getComments();
使用 'eager' 而不是 'lazy' 。 现在你没有使用'lazy' ..它不是一个真正的解决方案,如果你想使用lazy,就像一个临时(非常临时)的解决方法。
一般来说,最好的解决方案是1。
原因是当你使用延迟加载时,会话是关闭的。
有两种解决方案。
不要使用延迟加载。
在 XML 中设置lazy=false
或在注解中设置@OneToMany(fetch = FetchType.EAGER)
。
使用延迟加载。
在 XML 中设置lazy=true
或在注解中设置@OneToMany(fetch = FetchType.LAZY)
。
并在您的web.xml
中添加OpenSessionInViewFilter filter
详情见我的帖子。
为了延迟加载集合,必须有一个活动会话。 在 Web 应用程序中,有两种方法可以做到这一点。 您可以使用Open Session In View模式,在该模式中,您使用拦截器在请求开始时打开会话并在结束时关闭它。 存在的风险是您必须进行可靠的异常处理,否则您可能会绑定所有会话并且您的应用程序可能会挂起。
处理此问题的另一种方法是在控制器中收集您需要的所有数据,关闭会话,然后将数据填充到模型中。 我个人更喜欢这种方法,因为它似乎更接近 MVC 模式的精神。 此外,如果您以这种方式从数据库中收到错误,您可以比在视图渲染器中更好地处理它。 在这种情况下,您的朋友是Hibernate.initialize (myTopic.getComments())。 您还必须将对象重新附加到会话,因为您正在为每个请求创建一个新事务。 使用 session.lock(myTopic,LockMode.NONE) 。
最好的解决方案之一是在 application.properties 文件中添加以下内容: spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
如果您尝试在实体和 Collection 或 Java 对象列表(例如 Long 类型)之间建立关系,它会像这样:
@ElementCollection(fetch = FetchType.EAGER)
public List<Long> ids;
fetch = FetchType.LAZY
应该有两件事。
@Transactional
和
Hibernate.initialize(topicById.getComments());
这个延迟初始化问题有多种解决方案 -
1) 将关联 Fetch 类型从 LAZY 更改为 EAGER,但这不是一个好的做法,因为这会降低性能。
2)在关联对象上使用 FetchType.LAZY 并在服务层方法中使用事务注释,以便会话保持打开状态,当您调用 topicById.getComments() 时,将加载子对象(注释)。
3) 另外,请尝试在控制器层使用 DTO 对象而不是实体。 在您的情况下,会话在控制器层关闭。 所以最好在服务层将实体转换为 DTO。
我发现将@PersistenceContext
声明为EXTENDED
也可以解决这个问题:
@PersistenceContext(type = PersistenceContextType.EXTENDED)
在第二次执行生成 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()));
在我的情况下, @Transacctional
注释解决了它,
// 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()));
您的列表是延迟加载的,因此未加载列表。 打电话上榜是不够的。 在 Hibernate.initialize 中使用以初始化列表。 如果在列表元素上运行 dosnt 工作并为每个 . 这需要在您从事务范围返回之前。 看看这个帖子。
搜索 -
Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session
为了解决我的问题,它只是缺少这一行
<tx:annotation-driven transaction-manager="myTxManager" />
在应用程序上下文文件中。
未考虑方法上的@Transactional
注释。
希望答案会对某人有所帮助
控制器上的@Transactional 注释丢失
@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}
导致问题的原因是当关闭与数据库的“连接”时代码正在访问惰性 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;
}
这是我最近遇到的问题,我通过使用解决了
<f:attribute name="collectionType" value="java.util.ArrayList" />
这里有更详细的描述,这节省了我的时间。
通过使用休眠@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()
的事实会执行另一个查询以获取销售额,因为您明确要求它。
为了摆脱惰性初始化异常,您在操作分离对象时不应调用惰性收集。
在我看来,最好的方法是使用 DTO,而不是实体。 在这种情况下,您可以明确设置要使用的字段。 像往常一样就足够了。 无需担心诸如 jackson ObjectMapper
或 Lombok 生成的hashCode
之类的东西会隐式调用您的方法。
对于某些特定情况,您可以使用@EntityGrpaph
注释,即使您的实体中有eager
fetchType=lazy
,它也允许您进行即时加载。
这是一个老问题,但以下信息可能会帮助人们寻找答案。
@VladMihalcea 的回答很有用。 您不能依赖FetchType.EAGER
,而是应该在需要时将评论加载到Topic
实体中。
如果您没有明确定义查询以便可以指定join fetch
,那么使用@NamedEntityGraph
和@EntityGraph
您可以在运行时覆盖FetchType.LAZY
( @OneToMany
关联默认使用 LAZY)并同时加载评论仅在需要时作为Topic
。 这意味着您将评论限制为仅加载真正需要的那些方法(查询)。 JPA 定义的实体图:
实体图可以与 find 方法一起使用,也可以用作查询提示来覆盖或增加 FetchType 语义。
您可以根据此处的 JPA 示例使用它。 或者,如果您使用 Spring Data JPA,那么您可以根据Spring 提供的示例使用它。
对于那些使用Criteria的人,我发现
criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);
做了我需要做的一切。
集合的初始获取模式设置为 FetchMode.LAZY 以提供性能,但是当我需要数据时,我只需添加该行并享受完全填充的对象。
在我的情况下,以下代码是一个问题:
entityManager.detach(topicById);
topicById.getComments() // exception thrown
因为它与数据库分离,并且 Hibernate 在需要时不再从字段中检索列表。 所以我在分离之前初始化它:
Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm
不是最好的解决方案,但对于那些面临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();
原因是您在关闭服务内的会话后试图在控制器上获取评论列表。
topicById.getComments();
只有当你的休眠会话处于活动状态时,上面才会加载评论列表,我猜你在服务中关闭了它。
因此,您必须在关闭会话之前获取 commentList。
就我而言,我有 b/w A
和B
之类的映射
A
有
@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;
在DAO
层中,如果您没有使用Fetch Type - Eager注释映射,则需要使用@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
解决方案可以是:
使用fetch = FetchType.EAGER
注释comments
@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL) private Collection<Comment> comments = new LinkedHashSet<Comment>();
如果您仍然希望延迟加载评论,请使用Hibernate 的有状态会话,以便您稍后能够按需获取评论。
嗨,所有的帖子都迟到了,希望对其他人有所帮助,提前感谢 @GMK 的这篇帖子Hibernate.initialize(object)
当懒惰=“真”
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 会话后我也可以访问“设置”。
另一种方法是,您可以使用TransactionTemplate来包装惰性获取。 喜欢
Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());
在我的 Spring-Boot 项目spring.jpa.open-in-view
在 application.properties 中设置为 false。 将其设置为 true 解决了问题。
我有这个问题:
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循环一致
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.