在带联接的分页JPA查询之后出现的冗余请求
在测试通过 OrderRepository 获取 Order 实体数据的各种方法时,我发现对带分页的JPA查询存在冗余请求。
@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 实体的 Inquiry 实体,进行了N 次直接向数据库的请求,其中N 是与给定的 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<>();
}
有没有办法防止这种情况?使用不带分页的JPA语法的标准方法不会产生这种荒诞的现象。
@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<>();
}
在把一个 User 实体附加到每个 Order 实体时,会添加一个 User 实体,并以EAGER模式检索到的11个 Inquiry 实体。
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 ...
026-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方法语法,就应该避免分页。
解决方案
The redundant query behavior you are experiencing is caused by combining `JOIN FETCH` with `Pageable` execution when returning a `Page<T>` in Spring Data JPA.
To fulfill the `Page` contract, Spring Data JPA automatically generates and runs a secondary count query to calculate the total number of records. However, when Hibernate tries to translate your custom JPQL query into a count query, it often duplicates or mismanages the joins (especially collection fetches like `os.items` and related `User` / `Inquiry` associations), resulting in unnecessary extra queries or executing Cartesian products in memory.
Here are the two best approaches to eliminate these redundant requests:
### Solution 1: Provide a Custom Optimized `countQuery`
By explicitly separating the count logic, you force Spring Data JPA to evaluate total elements using a clean query that completely bypasses the heavy relationship fetches.
@Query(
value = "select os from Order os left join fetch os.items 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);
### Solution 2: Switch the Return Type to `Slice<Order>`
If your application or API contract does not strictly need to know the grand total of pages (e.g., if you are building a mobile "infinite scroll" or a simple "Load More" pagination), change the return type from `Page` to `Slice`.
@Query("select os from Order os left join fetch os.items where os.user.id = :userId")
Slice<Order> findByUserIdWithPagination(@Param("userId") Long userId, Pageable pageable);
**Why this works:** A `Slice` completely disables the automated count query execution. It only requests `pageSize + 1` rows from the database to check if a next page exists, saving critical database roundtrips and avoiding redundant relationship initialization.
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。