带关联的JPA分页查询出现冗余数据库请求问题求助
JPA分页关联查询出现冗余Inquiry实体查询的解决办法
在测试OrderRepository的Order实体分页查询方法时,发现执行带分页的JPA关联查询后,系统额外发起了N次针对User关联的Inquiry实体的查询(N为该用户关联的Inquiry数量),但Inquiry和当前仓库的查询逻辑无关。
问题代码
OrderRepository 代码
@Component(OrderRepository.BEAN_NAME) public interface OrderRepository extends JpaRepository<Order, Long> { // public static final BEAN_NAME... @Query(value = "select os from Order os left join fetch os.items osit left join fetch os.issues osis where os.user.id =:userId",countQuery = "select count(os) from Order os where os.user.id =:userId") Page<Order> findByUserIdWithPagination( @Param("userId") Long userId, Pageable pageable); }
User 实体代码
@Entity public class User { @Id Long id; String name; @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true) Set<Order> orders = new HashSet<>(); @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true) Set<Inquiry> inquiries = new HashSet<>(); }
Order 和 Inquiry 实体代码
@Entity public class Order { @Id Long id; String name; @ManyToOne @JoinColumn(name = "USER_ID") User user; @OneToMany(mappedBy = "order", cascade = CascadeType.REMOVE, orphanRemoval = true) @BatchSize(size = 10) Set<Item> items = new HashSet<>(); @OneToMany(mappedBy = "order", cascade = CascadeType.REMOVE, orphanRemoval = true) Set<Issue> issues; } @Entity public class Inquiry { @Id Long id; String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "USER_ID") User user; @OneToMany(mappedBy = "inquiry", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) Set<Item> items = new HashSet<>(); @OneToMany(mappedBy = "inquiry", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) Set<Issue> issues = new HashSet<>(); }
相关日志片段
2026-05-29 08:37:15.382 DEBUG 21276 --- [ main] org.hibernate.SQL : select order0_.id as... 2026-05-29 08:37:15.392 DEBUG 21276 --- [ main] org.hibernate.SQL : select user0_.id as ... 2026-05-29 08:37:15.393 DEBUG 21276 --- [ main] org.hibernate.SQL : select inquiry0_.id as ........ (x 11) 2026-05-29 08:05:52.778 INFO 20540 --- [ main] i.StatisticalLoggingSessionEventListener : Session Metrics { 344513 nanoseconds spent acquiring 1 JDBC connections; 0 nanoseconds spent releasing 0 JDBC connections; 1316376 nanoseconds spent preparing 13 JDBC statements; 3183940 nanoseconds spent executing 13 JDBC statements; 0 nanoseconds spent executing 0 JDBC batches; 0 nanoseconds spent performing 0 L2C puts; 0 nanoseconds spent performing 0 L2C hits; 0 nanoseconds spent performing 0 L2C misses; 0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections); 20064 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections) }
问题原因
JPA中@OneToMany默认抓取策略为FetchType.LAZY,出现冗余查询的核心原因是:
- 测试或业务代码中意外访问了User实体的
inquiries集合(如toString打印、序列化、调用getter方法),此时Hibernate Session未关闭,触发懒加载,导致N次额外查询。 - 分页查询场景下,Spring Data JPA会先执行count查询再执行数据查询,返回的Order关联User为代理对象,若后续操作触发懒加载属性,就会产生冗余请求。
解决办法
1. 显式声明User的inquiries集合为懒加载
虽然JPA默认@OneToMany是懒加载,但显式声明能避免潜在的配置问题:
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) Set<Inquiry> inquiries = new HashSet<>();
2. 避免在Session打开期间访问无关懒加载属性
检查测试代码和业务逻辑,确认是否有操作触发了inquiries集合的访问。如果不需要该集合,不要调用其getter方法,或在Session关闭后再处理实体对象。
3. 使用DTO投影,仅查询需要的字段
如果业务只需要Order及其关联的items、issues,无需完整User实体,可通过DTO投影避免加载无关属性:
// 定义DTO类 public class OrderDTO { private Long id; private String name; private Set<Item> items; private Set<Issue> issues; // 构造函数用于投影查询 public OrderDTO(Long id, String name, Set<Item> items, Set<Issue> issues) { this.id = id; this.name = name; this.items = items; this.issues = issues; } // getter方法 } // 修改Repository查询 @Query(value = "select new com.example.dto.OrderDTO(os.id, os.name, os.items, os.issues) from Order os left join fetch os.items osit left join fetch os.issues osis where os.user.id =:userId",countQuery = "select count(os) from Order os where os.user.id =:userId") Page<OrderDTO> findByUserIdWithPagination( @Param("userId") Long userId, Pageable pageable);
4. 使用实体图(Entity Graph)控制加载属性
若确实需要User实体,但不需要inquiries集合,可通过实体图指定仅加载必要属性:
// 在OrderRepository中添加实体图注解 @EntityGraph(attributePaths = {"items", "issues", "user"}, type = EntityGraph.EntityGraphType.FETCH) @Query(value = "select os from Order os where os.user.id =:userId",countQuery = "select count(os) from Order os where os.user.id =:userId") Page<Order> findByUserIdWithPagination( @Param("userId") Long userId, Pageable pageable);
该方式会让Hibernate仅加载User的基础属性,不会触发inquiries集合的懒加载。
总结
不需要完全避免分页查询,只要通过控制懒加载触发条件、使用DTO投影或实体图精准控制加载属性,就能解决冗余数据库请求问题。
内容的提问来源于stack exchange,提问作者haoz
相关产品推荐
相关产品推荐

