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
相关产品推荐
相关产品推荐

