1. 简介

在本教程中,我们将学习如何在 JPASpring 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. 使用 firsttop

我们可以使用方法名推导机制,通过关键字 firsttop 来限制查询结果数量。

可以指定一个数字表示最大返回数量,如果不指定,则默认为 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 的方法名中使用 firsttop

从 Spring Data JPA 3.2 开始,还可以使用 Limit 接口的 of() 方法来控制结果数量。

一如既往,代码示例可以在 GitHub 上找到。


原始标题:Limiting Query Results with JPA and Spring Data JPA | Baeldung