JPA查询未关联Fare却自动加载该关联数据的问题排查
JPA关联查询意外加载关联属性问题
我在JPA查询中遇到了一个问题:明明没在查询里关联Schedule类的fare属性,但结果里自动加载了fare及其关联数据。我希望只有显式声明JOIN或JOIN FETCH时才加载该数据,试了改用普通JOIN也没用。
实体类代码
Schedule类
@Entity @Table(name="schedules") public class Schedule { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") private int id; @Column(name="source_dep_time") private Time sourceDepTime; @Column(name="dest_arr_time") private Time destArrTime; @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) @JoinColumn(name="plane_id") private Plane plane; @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) @JoinColumn(name="source_id") private Airport source; @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) @JoinColumn(name="destination_id") private Airport destination; @OneToMany(fetch = FetchType.LAZY, mappedBy="schedule", cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) private List<Fare> fare; }
Fare类
@Entity @Table(name="fare") public class Fare { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="id") private int id; @Column(name="fare") private double fare; @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) @JoinColumn(name="class_id") private Classes classType; @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) @JsonBackReference @JoinColumn(name="schedule_id") private Schedule schedule; }
查询代码
public interface ReservationRepository extends JpaRepository<Reservation, Integer> { @Query("from Reservation r join fetch r.ticket t join fetch r.payment join t.schedule sc join fetch sc.source join fetch sc.destination join fetch sc.plane pl " + "join fetch pl.airline join fetch r.passengers p join fetch p.seat s join s.classes where t.pnr = ?1") Reservation getReservationDetails(int pnr); }
生成的SQL(手动格式化)
select reservatio0_.id as id1_8_0_, ticket1_.id as id1_11_1_, payment2_.id as id1_5_2_, schedule3_.id as id1_9_3_, airport4_.id as id1_1_4_, airport5_.id as id1_1_5_, plane6_.id as id1_7_6_, airline7_.id as id1_0_7_, passengers8_.id as id1_4_8_, seat9_.id as id1_10_9_, classes10_.id as id1_2_10_, reservatio0_.journey_date as journey_2_8_0_, reservatio0_.payment_id as payment_4_8_0_, reservatio0_.res_date_time as res_date3_8_0_, reservatio0_.ticket_id as ticket_i5_8_0_, ticket1_.pnr as pnr2_11_1_, ticket1_.schedule_id as schedule3_11_1_, payment2_.amount_paid as amount_p2_5_2_, schedule3_.dest_arr_time as dest_arr2_9_3_, schedule3_.destination_id as destinat4_9_3_, schedule3_.plane_id as plane_id5_9_3_, schedule3_.source_id as source_i6_9_3_, schedule3_.source_dep_time as source_d3_9_3_, airport4_.city as city2_1_4_, airport4_.city_code as city_cod3_1_4_, airport5_.city as city2_1_5_, airport5_.city_code as city_cod3_1_5_, plane6_.airline_id as airline_3_7_6_, plane6_.plane_no as plane_no2_7_6_, airline7_.name as name2_0_7_, passengers8_.age as age2_4_8_, passengers8_.gender as gender3_4_8_, passengers8_.name as name4_4_8_, passengers8_.phone as phone5_4_8_, passengers8_.seat_id as seat_id6_4_8_, passengers8_.reservation_id as reservat7_4_0__, passengers8_.id as id1_4_0__, seat9_.class_id as class_id3_10_9_, seat9_.seat_no as seat_no2_10_9_, classes10_.type as type2_2_10_ from reservations reservatio0_ inner join tickets ticket1_ on reservatio0_.ticket_id=ticket1_.id inner join payments payment2_ on reservatio0_.payment_id=payment2_.id inner join schedules schedule3_ on ticket1_.schedule_id=schedule3_.id inner join airports airport4_ on schedule3_.source_id=airport4_.id inner join airports airport5_ on schedule3_.destination_id=airport5_.id inner join planes plane6_ on schedule3_.plane_id=plane6_.id inner join airlines airline7_ on plane6_.airline_id=airline7_.id inner join passengers passengers8_ on reservatio0_.id=passengers8_.reservation_id inner join seats seat9_ on passengers8_.seat_id=seat9_.id inner join classes classes10_ on seat9_.class_id=classes10_.id where ticket1_.pnr=?
解决方案
排查懒加载触发点
懒加载失效大概率是代码在持久化上下文关闭前,意外访问了Schedule.fare的getter方法——比如接口返回实体时,JSON序列化框架(如Jackson)会遍历所有属性并调用getter,直接触发懒加载。- 解决:在
Schedule.fare上添加@JsonIgnore阻止自动序列化;或者改用DTO对象,只封装需要的字段,不直接返回实体类。 - 检查代码中是否有日志打印、调试代码调用了
schedule.getFare(),这类操作也会触发懒加载。
- 解决:在
验证JPA懒加载实现
Hibernate等JPA提供者依赖动态代理实现懒加载,如果实体类被final修饰,或存在类加载器冲突,会导致懒加载失效转为立即加载。确认你的实体类没有被final修饰。用DTO完全控制返回数据
在JPQL中使用SELECT NEW构造自定义DTO,只选择需要的字段,彻底避免加载无关关联:@Query("SELECT NEW com.yourpackage.ReservationDTO(r.id, t.pnr, sc.id, sc.sourceDepTime, ...) " + "from Reservation r join fetch r.ticket t join fetch r.payment join t.schedule sc join fetch sc.source join fetch sc.destination join fetch sc.plane pl " + "join fetch pl.airline join fetch r.passengers p join fetch p.seat s join s.classes where t.pnr = ?1") ReservationDTO getReservationDetails(int pnr);这种方式最可靠,完全由你控制返回的数据范围。
检查全局配置是否覆盖懒加载
部分框架或配置会全局设置FetchType.EAGER,比如Spring Boot的默认配置、Hibernate的hibernate.default_fetch_type属性。检查application.properties或persistence.xml,确保没有覆盖懒加载的默认设置。
内容的提问来源于stack exchange,提问作者Akash
相关产品推荐
相关产品推荐

