为啥加载惰性集合
Posted
技术标签:
【中文标题】为啥加载惰性集合【英文标题】:why the lazy collection is loaded为什么加载惰性集合 【发布时间】:2016-06-25 04:22:42 【问题描述】:我有一个与 Event 实体具有 oneToMany 关系的 Project 实体
public class Project
....
@OneToMany(mappedBy = "dossier", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Event> events;
我有一个 ProjectService 类
@Service
@Transactional
public class ProjectService
public List<Project> findAll()
return (List<Project>) projectRepository.findAll();
还有一个项目控制器
@RestController
@RequestMapping(value = "/projects")
public class ProjectController
@RequestMapping(method= RequestMethod.GET)
public List<Project> getAllProject()
return projectService.findAll();
在我的客户端代码中,我看到项目的事件已加载,但我不明白为什么。
我预计在 DossierService 中的方法 findAll 的事务结束时,实体将被分离。显然,我的实体仍然附加,因为在我的控制器中的杰克逊序列化期间检索事件。
【问题讨论】:
你到底想问什么? 我不明白为什么在集合是 Lazy 时加载事件 【参考方案1】:Project.events
默认是延迟加载的,因为它是OneToMany
关系。
这并不意味着没有加载Project.events
。这意味着它将在调用Project.getEvents()
时立即加载。
这发生在 JSON 序列化时(ProjectController.getAllProject()
返回其响应时)。
为了防止这种情况,有两种方法:
在ProjectService
返回的每个项目上显式调用project.setEvents(null)
(或一个空列表)。
或者您在Project.events
上添加@JsonIgnore
注释。
编辑:如果您使用的是spring-boot,则默认注册OpenEntityManagerInViewInterceptor:
Spring Web 请求拦截器,将 JPA EntityManager 绑定到线程以完成请求的整个处理。用于“在视图中打开 EntityManager”模式,即允许在 Web 视图中延迟加载,尽管原始事务已经完成。
您可以通过将此行添加到 application.properties
来禁用此行为:
spring.jpa.open-in-view=false
使用此配置,在休眠会话之外调用 getter 将导致 LazyInitializationException
。
【讨论】:
我一直认为,当你在事务之外第一次尝试访问延迟加载的实体时,你会得到一个 null 与@TomasKralik 相同,为什么 getEvents 在事务之外起作用。 ? AFAIK,这取决于您的 ORM。使用休眠,你会得到一个org.hibernate.LazyInitializationException
。使用 iBatis,您可能会得到 null,但我没有测试。
@ArnaudDenoyelle 我不明白为什么在序列化过程中我的控制器中没有出现 LazyInitializationException。我希望在我的控制器中关闭持久性上下文,显然它在调用方法 findAll 后仍然打开,因为事件集合被正确检索。 spring 何时打开和关闭持久性上下文? (我以为是每次交易开始和结束的时候)
@oliv37 如果您使用的是 spring-boot,它会自动注册一个 OpenEntityManagerInViewInterceptor
,它将 JPA EntityManager 绑定到线程以用于整个请求处理。如果要防止这种行为,可以在 application.properties 中添加spring.jpa.open-in-view=false
。然后,您将获得预期的LazyInitializationException
。【参考方案2】:
有两种可能:
已定义 OpenSessionInView
或 OpenEntityManagerInView
bean。这种类型的 bean 分别导致 Session
或 EntityManager
在控制器被调用时打开并保持打开状态,直到控制器的主体被序列化。
它在演示/小型应用程序中通常被认为是可接受的行为,但在更大更复杂的应用程序中却非常不受欢迎,因为您的查询应该返回视图、控制器或逻辑所需的完全初始化的实体。
JSON 库启用了休眠插件,它能够在序列化过程中重新附加和水合实体。
否则,您期望Session
/EntityManager
已关闭并且实体在服务返回后分离的默认行为是准确的。否则,预期的行为将是 LazyInitializationException
。
【讨论】:
Spring-boot 确实默认添加了OpenEntityManagerInViewInterceptor
(可配置)。【参考方案3】:
您的实体仍处于连接状态,直到:
您要求实体管理器使用 entityManager.clear() 清除持久性上下文 您要求实体经理对每个项目使用 entityManager.detach(project) 分离您的实体。但如果您知道您的事件大部分时间都会被加载,那么您应该考虑使用 FetchType.EAGER 以便一次获取所有内容。
【讨论】:
我希望 spring 在事务结束时关闭持久性上下文(在 DossierService 中调用 findAll() 之后),我不明白为什么我的控制器中仍然打开持久性上下文。 spring 什么时候打开和关闭持久化上下文?以上是关于为啥加载惰性集合的主要内容,如果未能解决你的问题,请参考以下文章
Hibernate:为啥 FetchType.LAZY 注释的集合属性急切地加载?