使用@WebAppConfiguration时Spring Data JPA懒加载失效问题
先把涉及的代码都贴出来,方便对照分析:
实体模型
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; private String username; @ManyToMany(fetch = FetchType.LAZY) @JoinTable( name = "USER_ROLE", joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")} ) private List<Role> roles; (...) } @Entity public class Role { private String name; @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY) private List<User> users; (...) }
数据仓库
public interface UserRepository extends CrudRepository<User, Long> { User findByUsername(String username); }
控制器代码
@RestController @RequestMapping("/test") public class Controller { @Autowired private UserRepository userRepository; @GetMapping public void test(@RequestParam final String u) { if(!this.userRepository.findByUsername(u) .getRoles().get(0) .getUsers().get(0) .getRoles().get(0) .getName().equals("Role1")) throw new RuntimeException(); } }
主启动类
@SpringBootApplication public class Application { public static void main(final String[] args) throws Exception { SpringApplication.run(Application.class, args); } }
配置文件(application.properties)
spring.jpa.open-in-view=false
原测试类
@RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration(classes = Application.class) @AutoConfigureMockMvc public class ControllerTest { @Autowired private MockMvc mockMvc; @Test public void test() throws Exception { this.mockMvc.perform(get("/test").param("u", "user1")) .andExpect(status().isOk()); } }
你的问题核心
按道理,仓库方法执行完事务就该结束,加上你已经关闭了spring.jpa.open-in-view,控制器里访问roles、user->role->users这些懒加载关联时,应该直接抛出LazyInitializationException才对,但实际调试时发现所有关联都被急加载了,测试还顺利通过。更奇怪的是,把@WebAppConfiguration换成@SpringBootTest后,就立刻抛出了预期的异常。
从Hibernate日志也能看到,查询用户时直接把User.roles给加载完了,完全没遵循FetchType.LAZY的设置。
问题根源:两种测试注解的上下文逻辑差异
这其实是@WebAppConfiguration和@SpringBootTest在初始化测试上下文时的规则不同导致的:
@WebAppConfiguration的事务与EntityManager生命周期
当你用@WebAppConfiguration + @ContextConfiguration时,测试的上下文并没有完整加载Spring Boot的所有自动配置逻辑。MockMvc处理请求时,仓库方法的事务虽然在方法结束后标记为终止,但Hibernate的EntityManager并没有立刻关闭——它的生命周期被延长到了整个请求处理的全程,包括控制器方法执行的阶段。
这就意味着,当你在控制器里调用getRoles()、getUsers()时,EntityManager还是活跃的,Hibernate会自动触发关联的加载,自然不会抛出LazyInitializationException。@SpringBootTest的严格配置遵循@SpringBootTest是Spring Boot官方推荐的集成测试注解,它会完整加载Spring Boot的所有自动配置,包括你设置的spring.jpa.open-in-view=false。
在这种模式下,仓库方法执行完毕后事务立即结束,EntityManager被及时关闭。控制器里再访问懒加载关联时,因为没有活跃的会话(Session)可以用来加载数据,就会抛出LazyInitializationException,这才是符合你预期的懒加载行为。Open Session in View的隐性生效
虽然你配置了spring.jpa.open-in-view=false,但在@WebAppConfiguration的测试场景中,这个配置可能没有被正确读取和应用,相当于变相开启了Open Session in View模式——会话在整个请求周期保持打开,允许随时加载懒加载关联。而@SpringBootTest会严格解析并应用你的配置,确保Open Session in View处于关闭状态。
总结
简单来说,@WebAppConfiguration的测试方式更偏向传统Spring MVC的测试逻辑,没有完全适配Spring Boot的JPA事务管理规则,导致懒加载的限制没有生效;而@SpringBootTest会严格遵循你的配置,表现出符合预期的行为。如果要测试懒加载的正确性,建议使用@SpringBootTest来编写集成测试。
内容的提问来源于stack exchange,提问作者Whimusical

