为啥加载惰性集合

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】:

有两种可能:

    已定义 OpenSessionInViewOpenEntityManagerInView bean。这种类型的 bean 分别导致 SessionEntityManager 在控制器被调用时打开并保持打开状态,直到控制器的主体被序列化。

    它在演示/小型应用程序中通常被认为是可接受的行为,但在更大更复杂的应用程序中却非常不受欢迎,因为您的查询应该返回视图、控制器或逻辑所需的完全初始化的实体。

    JSON 库启用了休眠插件,它能够在序列化过程中重新附加和水合实体。

否则,您期望Session/EntityManager 已关闭并且实体在服务返回后分离的默认行为是准确的。否则,预期的行为将是 LazyInitializationException

【讨论】:

Spring-boot 确实默认添加了OpenEntityManagerInViewInterceptor(可配置)。【参考方案3】:

您的实体仍处于连接状态,直到:

您要求实体管理器使用 entityManager.clear() 清除持久性上下文 您要求实体经理对每个项目使用 entityManager.detach(project) 分离您的实体。

但如果您知道您的事件大部分时间都会被加载,那么您应该考虑使用 FetchType.EAGER 以便一次获取所有内容。

【讨论】:

我希望 spring 在事务结束时关闭持久性上下文(在 DossierService 中调用 findAll() 之后),我不明白为什么我的控制器中仍然打开持久性上下文。 spring 什么时候打开和关闭持久化上下文?

以上是关于为啥加载惰性集合的主要内容,如果未能解决你的问题,请参考以下文章

停止加载子集合,如果我更新父集合

Scala编程之惰性函数

Hibernate:为啥 FetchType.LAZY 注释的集合属性急切地加载?

通过惰性列表加载图像?

如何使用 SQLAlchemy 找出是不是还没有加载惰性关系?

Django惰性加载和LazyObject