手动传入ID时Hibernate(LAZY模式)为何触发关联查询
问题描述
我在Student类中手动传入ID,所有实体关联都配置为LAZY加载模式,但Spring Data JPA(Hibernate)执行SELECT查询时自动关联所有关联表,而非仅查询student表,且该操作被视为merge请求,无法理解此现象。
生成的SQL
select student0_.uuid as uuid1_2_3_, student0_.address_uuid as address_3_2_3_, student0_.name as name2_2_3_, address1_.uuid as uuid1_0_0_, address1_.city_uuid as city_uui5_0_0_, address1_.lat as lat2_0_0_, address1_.lon as lon3_0_0_, address1_.pincode as pincode4_0_0_, city2_.uuid as uuid1_1_1_, city2_.name as name2_1_1_, subjects3_.student_uuid as student_4_3_5_, subjects3_.uuid as uuid1_3_5_, subjects3_.uuid as uuid1_3_2_, subjects3_.course_start_date as course_s2_3_2_, subjects3_.name as name3_3_2_, subjects3_.student_uuid as student_4_3_2_ from student student0_ inner join address address1_ on student0_.address_uuid=address1_.uuid left outer join city city2_ on address1_.city_uuid=city2_.uuid left outer join subject subjects3_ on student0_.uuid=subjects3_.student_uuid where student0_.uuid=?
实体类代码
Student实体类
@Entity public class Student { @Id private UUID uuid; @Column(nullable = false) private String name; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false) private Address address; @OneToMany(mappedBy = "student", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Subject> subjects; }
Subject实体类
@Entity public class Subject { @Id @GeneratedValue private UUID uuid; @Column(nullable = false) private String name; @Column(nullable = false) private ZonedDateTime courseStartDate; @ManyToOne(fetch = FetchType.EAGER) private Student student; }
City实体类
@Entity public class City { @Id @GeneratedValue private UUID uuid; @Column(nullable = false) private String name; }
Address实体类
@Entity public class Address { @Id @GeneratedValue private UUID uuid; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private City city; @Column(nullable = false) private String pincode; @Column private double lat; @Column private double lon; }
测试用例
@ExtendWith(SpringExtension.class) @DataJpaTest @AutoConfigureTestDatabase(replace = NONE) public class StudentRepositoryIT { @PersistenceContext private EntityManager entityManager; @Rollback(value = false) @Test public void should_fetch_student() { // GIVEN UUID uuid = UUID.randomUUID(); Student john = new Student().setName("John Wick").setUuid(uuid); City newyork = new City().setName("Newyork City"); Address address = new Address().setCity(newyork) .setLat(12.3) .setLon(13.4) .setPincode("010101"); john.setAddress(address); Subject maths = new Subject().setName("maths").setCourseStartDate(ZonedDateTime.now()).setStudent(john); Subject english = new Subject().setName("english").setCourseStartDate(ZonedDateTime.now()).setStudent(john); Subject hindi = new Subject().setName("hindi").setCourseStartDate(ZonedDateTime.now()).setStudent(john); Subject geology = new Subject().setName("geology").setCourseStartDate(ZonedDateTime.now()).setStudent(john); Subject physics = new Subject().setName("physics").setCourseStartDate(ZonedDateTime.now()).setStudent(john); Subject science = new Subject().setName("science").setCourseStartDate(ZonedDateTime.now()).setStudent(john); john.setSubjects(List.of(maths, english, hindi, geology, physics, science)); studentRepository.saveAndFlush(john); TestTransaction.end(); // WHEN TestTransaction.start(); Student student = entityManager.find(Student.class, uuid); System.out.println(student); System.out.println(student.getAddress()); Address address1 = student.getAddress(); System.out.println(address1.getLat()); // THEN check query console } }
实体关系图

问题原因及解决方案
核心原因
- Subject的EAGER关联触发链式加载:Subject类中
@ManyToOne(fetch = FetchType.EAGER)的配置,会让Hibernate在加载Student时反向加载所有关联的Subject;而Subject又关联Student,为避免循环引用,Hibernate会用JOIN查询一次性加载所有关联数据,连带触发Address、City的加载。 - OneToOne的LAZY加载限制:
@OneToOne(optional = false)的关联,Hibernate默认无法实现真正的LAZY加载(需要立即判断关联对象是否存在以满足optional=false约束),会自动转为EAGER加载,这是Address被强制JOIN查询的关键原因。 - 打印操作触发懒加载初始化:测试用例中
System.out.println(student)和System.out.println(student.getAddress)会调用实体的toString方法,触发懒加载代理初始化,强制加载关联数据。
解决方案
- 将Subject的关联改为LAZY:修改Subject类中的
@ManyToOne配置:
@ManyToOne(fetch = FetchType.LAZY) private Student student;
- 修复OneToOne的LAZY加载:为
@OneToOne(optional = false)添加@LazyToOne(LazyToOneOption.NO_PROXY)(需开启Hibernate字节码增强),或者将optional改为true(业务允许的情况下):
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false) @LazyToOne(LazyToOneOption.NO_PROXY) private Address address;
- 优化打印逻辑:避免直接打印整个实体,只打印必要字段,防止触发懒加载初始化。
内容的提问来源于Stack Exchange,提问作者Sagar Rout
相关产品推荐
相关产品推荐

