You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring MVC控制器为何忽略FetchType.LAZY,表现为EAGER加载?

问题

我已经给Film实体的Actor关联明确配置了懒加载(FetchType.LAZY),代码如下:

@Entity
@Getter
@Setter
public class Film {

...

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
        name = "film_actor",
        joinColumns = @JoinColumn(name = "film_id"),
        inverseJoinColumns = @JoinColumn(name = "actor_id")
)
private List<Actor> cast = new ArrayList<>();

在其他上下文(比如下面的run方法)中调用服务/仓库时,懒加载功能正常:

@Override
public void run(String... args) throws Exception {
    List<Film> films = filmService.getAllFilms();
    System.out.println(films);
}

但在使用@Controller、@RequestMapping等注解的Spring MVC控制器方法中,这个关联却始终被"急加载",代码如下:

@GetMapping("")
public String filmsPage(Model model){
    List<Film> allMyFilms = filmService.getAllFilms();
    model.addAttribute("films", allMyFilms);
    return "film/film-list";
}

服务层代码:

@Service
@RequiredArgsConstructor
public class FilmServiceImpl implements FilmService {
private final FilmRepository filmRepo;

...

@Override
public List<Film> getAllFilms() {
    List<Film> films = filmRepo.findAll();
    return films;
}

仓库层代码:

@Repository
public interface FilmRepository extends JpaRepository<Film, Long> {
    List<Film> findAll();
}

请问这是什么原因?如何修改以恢复懒加载?如果要一次性展示1000部影片,我不想加载大量关联的Actor实体。


原因分析

核心原因是视图渲染触发了懒加载关联的初始化:

  • 在run方法中,System.out.println(films)仅调用Film的默认toString方法(未重写的话),不会访问cast集合,所以懒加载没被触发。
  • 而在Spring MVC场景中,当你把films放入Model后,视图模板(比如Thymeleaf、JSP)渲染页面时,会遍历films并访问cast集合的属性(比如模板中写${film.cast}或遍历演员列表),此时Hibernate的懒加载代理会自动触发数据库查询,看起来像是被急加载了。
  • 另外,Spring Boot默认开启的**Open Session in View(OSIV)**模式,会让Hibernate会话在视图渲染阶段保持打开状态,所以视图访问懒加载属性时不会抛出LazyInitializationException,进一步加深了"关联被急加载"的错觉。

解决方案

1. 禁止视图访问懒加载属性

检查你的视图模板(如film-list.html),确保没有直接调用Film的cast集合。如果页面只需要展示影片基本信息(标题、上映时间等),完全不需要涉及${film.cast}相关代码,懒加载就不会被触发。

2. 使用DTO封装视图所需数据(推荐)

创建只包含页面需要字段的FilmDTO,在服务层将实体转换为DTO后再传递给视图,从根源避免懒加载触发:

// 定义FilmDTO
public class FilmDTO {
    private Long id;
    private String title;
    // 仅保留页面需要的字段,不包含Actor相关属性

    // 构造方法、getter/setter
    public FilmDTO(Film film) {
        this.id = film.getId();
        this.title = film.getTitle();
    }
}

// 修改服务层方法
@Override
public List<FilmDTO> getAllFilms() {
    return filmRepo.findAll().stream()
            .map(FilmDTO::new)
            .collect(Collectors.toList());
}

// 修改控制器方法
@GetMapping("")
public String filmsPage(Model model){
    List<FilmDTO> allMyFilms = filmService.getAllFilms();
    model.addAttribute("films", allMyFilms);
    return "film/film-list";
}

这种方式既符合分层架构设计,又能完全控制视图能访问的数据。

3. 显式控制查询结果

在仓库层定义JPQL查询,仅查询Film的基本字段,避免加载关联的Actor:

@Repository
public interface FilmRepository extends JpaRepository<Film, Long> {
    @Query("SELECT f FROM Film f")
    List<Film> findAllWithoutActors();
}

之后在服务层调用该方法即可确保不会加载Actor关联。

4. 关闭Open Session in View(需评估影响)

如果不需要OSIV模式,可以通过配置关闭:

spring.jpa.open-in-view=false

但关闭后,若视图不小心访问懒加载属性会直接抛出LazyInitializationException,需确保所有视图需要的数据都在服务层提前准备好。


内容的提问来源于stack exchange,提问作者Ghoti and Chips

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:51:18