You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带关联的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 06:57:27