1. 简介
在本教程中,我们将学习如何在 JPA 和 Spring Data JPA 中限制查询结果的数量。
首先,我们会看下要查询的表结构,以及我们希望复现的 SQL 查询语句。
接着,我们将深入探讨如何使用 JPA 和 Spring Data JPA 来实现相同的效果。
2. 测试数据
以下是我们本文中将要查询的表:
我们要回答的问题是:“第一个被占用的座位是哪一个,谁坐在那里?”
First Name | Last Name | Seat Number |
---|---|---|
Jill | Smith | 50 |
Eve | Jackson | 94 |
Fred | Bloggs | 22 |
Ricki | Bobbie | 36 |
Siya | Kolisi | 85 |
3. SQL 查询语句
用 SQL 写的话,我们可能会写出如下查询语句:
SELECT firstName, lastName, seatNumber FROM passengers ORDER BY seatNumber LIMIT 1;
4. 使用 JPA 实现
使用 JPA 时,我们首先需要一个实体类来映射这张表:
@Entity
class Passenger {
@Id
@GeneratedValue
@Column(nullable = false)
private Long id;
@Basic(optional = false)
@Column(nullable = false)
private String firstName;
@Basic(optional = false)
@Column(nullable = false)
private String lastName;
@Basic(optional = false)
@Column(nullable = false)
private int seatNumber;
// constructor, getters etc.
}
接下来,我们需要一个方法来封装我们的查询逻辑,这里我们实现为 PassengerRepositoryImpl#findOrderedBySeatNumberLimitedTo(int limit)
:
@Repository
class PassengerRepositoryImpl {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Passenger> findOrderedBySeatNumberLimitedTo(int limit) {
return entityManager.createQuery("SELECT p FROM Passenger p ORDER BY p.seatNumber",
Passenger.class).setMaxResults(limit).getResultList();
}
}
在这个 Repository 方法中,我们通过 EntityManager 创建了一个 Query 对象,并调用 setMaxResults() 方法来限制返回结果的数量。
这个调用最终会生成类似下面的 SQL:
select
passenger0_.id as id1_15_,
passenger0_.first_name as first_nam2_15_,
passenger0_.last_name as last_nam3_15_,
passenger0_.seat_number as seat_num4_15_
from passenger passenger0_ order by passenger0_.seat_number limit ?
✅ 踩坑提醒:别忘了 JPQL 中不能直接写 LIMIT
,必须通过 setMaxResults()
来控制。
5. 使用 Spring Data JPA 实现
Spring Data JPA 提供了多种方式来限制查询结果。
5.1. 使用 first
或 top
我们可以使用方法名推导机制,通过关键字 first
或 top
来限制查询结果数量。
可以指定一个数字表示最大返回数量,如果不指定,则默认为 1。
例如,要获取第一个被占用的座位信息,可以这样写:
Passenger findFirstByOrderBySeatNumberAsc();
Passenger findTopByOrderBySeatNumberAsc();
如果只返回一个结果,我们也可以使用 Optional
包装返回值:
Optional<Passenger> findFirstByOrderBySeatNumberAsc();
Optional<Passenger> findTopByOrderBySeatNumberAsc();
5.2. 使用 Pageable
另一种方式是使用 Pageable 对象:
Page<Passenger> page = repository.findAll(
PageRequest.of(0, 1, Sort.by(Sort.Direction.ASC, "seatNumber")));
查看 Spring Data JPA 默认实现类 SimpleJpaRepository 的源码,会发现它也是通过调用 Query#setMaxResults 来实现分页的:
protected <S extends T > Page < S > readPage(TypedQuery < S > query,
Class < S > domainClass, Pageable pageable,
@Nullable Specification < S > spec) {
if (pageable.isPaged()) {
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
}
return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> {
return executeCountQuery(this.getCountQuery(spec, domainClass));
});
}
5.3. 使用 Limit
(Spring Data JPA 3.2+)
从 Spring Data JPA 3.2 开始,新增了一个 Limit
类型用于限制查询结果数量。它提供了两个静态方法:
of(int)
:指定最大返回数量unlimited()
:不限制返回数量
示例:使用 Limit.of(1)
来限制结果为 1 条:
List<Passenger> passenger = repository.findByOrderBySeatNumberAsc(Limit.of(1));
这里我们通过 Limit.of(1)
明确指定了只返回一条记录。
5.4. 方式对比
这些方式最终生成的 SQL 是一样的:
select
passenger0_.id as id1_15_,
passenger0_.first_name as first_nam2_15_,
passenger0_.last_name as last_nam3_15_,
passenger0_.seat_number as seat_num4_15_
from passenger passenger0_ order by passenger0_.seat_number asc limit ?
不同方式的适用场景:
方式 | 特点 |
---|---|
first/top |
约定优于配置,适合简单场景 ✅ |
Pageable |
配置灵活,适合复杂分页 ❌ |
Limit |
简洁直观,适合 Spring Boot 3.2+ ⚠️ |
6. 总结
在 JPA 中限制查询结果的方式与 SQL 有所不同:我们不能直接在 JPQL 中写 LIMIT
关键字。
取而代之的是通过调用 Query#setMaxResults 方法,或者在 Spring Data JPA 的方法名中使用 first
或 top
。
从 Spring Data JPA 3.2 开始,还可以使用 Limit
接口的 of()
方法来控制结果数量。
一如既往,代码示例可以在 GitHub 上找到。