背景

这是个非常实用的视角,掌握 Spring Data JPA、JpaRepository 概念以及方法命名规范。在使用 Spring Boot 构建微服务时,数据持久层的开发是日常任务之一。Spring Data JPA 是 Spring 提供的一种基于 JPA 的 ORM 数据访问抽象,极大地简化了我们对数据库的操作。你甚至不需要写 SQL,就能完成基本的 CRUD 逻辑。

1. 什么是 JPA?

JPA(Java Persistence API)是 Java 提供的一套 ORM(对象关系映射)规范,由接口组成,没有具体实现。主流实现有:

  • Hibernate(最常见)
  • EclipseLink
  • OpenJPA

2. 什么是 Spring Data JPA?

Spring Data JPA 是 Spring 基于 JPA 做的再封装,让开发更自动化、声明化。Spring Data JPA 的目标是通过 最少的代码 实现 最强的数据访问层能力。它封装了 JPA 的复杂性,并通过接口编程 + 方法命名规则,让我们不用写 SQL 也能完成查询。

3. JpaRepository 是什么?

JpaRepository 是 Spring Data JPA 提供的核心接口,它继承了 PagingAndSortingRepository 和 CrudRepository,提供了大量的 现成方法:

方法名 功能
save() 保存或更新实体
findById() 根据 ID 查询
findAll() 查询所有记录
deleteById() 根据 ID 删除
count() 查询总数
existsById() 判断是否存在

4. 自定义方法命名规则

Spring Data JPA 最大的魔法在于:根据方法名自动生成 SQL 语句。比如:

1
2
3
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByUsername(String username); // 自动生成 SQL
}

等价于 SQL:

1
SELECT COUNT(*) > 0 FROM user WHERE username = ?

方法名的构成公式

1
<查询关键词><By><字段条件表达式><附加条件>

查询关键词

关键词 含义
findBy 查询
existsBy 是否存在
deleteBy 删除
countBy 计数
readBy 查询(同 findBy、getBy)

条件关键词

关键词 含义与示例
Is, Equals 等于(可以省略)
如:findByUsername(String name),相当于 =
IsNot, Not 不等于
如:findByAgeNot(Integer age),相当于 !=
Like 模糊匹配(%值%)
如:findByUsernameLike(String name) ⇒ %name%
NotLike 模糊不匹配
如:findByUsernameNotLike(String name)
StartingWith 以…开头
如:findByUsernameStartingWith("A") ⇒ A%
EndingWith 以…结尾
如:findByUsernameEndingWith("n") ⇒ %n
Containing 包含
如:findByUsernameContaining("oh") ⇒ %oh%
In 在集合中
如:findByIdIn(List<Long> ids) ⇒ IN (...)
NotIn 不在集合中
如:findByRoleNotIn(List<String> roles)
Between 范围查询
如:findByAgeBetween(18, 30)
LessThan, GreaterThan 小于 / 大于
如:findByAgeLessThan(20),findByAgeGreaterThan(30)
Before, After 时间前 / 后
如:findByCreatedAtBefore(Date d),findByCreatedAtAfter(Date d)
IsNull, IsNotNull 是否为空 / 非空
如:findByEmailIsNull(),findByEmailIsNotNull()
True, False 布尔值判断
如:findByEnabledTrue(),findByDeletedFalse()

条件连接词

关键词 含义
And 与
Or 或
Between 在两个值之间
GreaterThan, LessThan 比较大小
Like 模糊匹配

附加关键词

关键词 含义
Top1 / TopN 取前 1 条 / 前 N 条数据
First 第一个
Distinct 去重查询
OrderBy 排序

示例

1
2
3
4
List<User> findByEmailAndUsername(String email, String username);
User findByUsername(String username);
List<User> findByCreatedAtAfter(Date date);
boolean existsByEmail(String email);    
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 分页与排序 示例

public interface UserRepository extends JpaRepository<User, Long> {

    // 按用户名模糊匹配,并分页,分页查询推荐 Pageable
    Page<User> findByUsernameContaining(String keyword, Pageable pageable);

    // 按年龄大于某值,并按年龄倒序
    List<User> findByAgeGreaterThan(int age, Sort sort);
}

@Autowired
private UserRepository userRepository;

public void testPagination() {
    int page = 0;
    int size = 10;
    Pageable pageable = PageRequest.of(page, size, Sort.by("age").descending());

    Page<User> result = userRepository.findByUsernameContaining("john", pageable);

    result.getContent().forEach(System.out::println);
}

# 常用分页构造法
PageRequest.of(int page, int size)  // 默认按 id 升序

PageRequest.of(int page, int size, Sort.by("age").descending())
1
2
# 查询年龄在18到30之间、名字包含"tao",并且邮箱不为空的用户
List<User> findByAgeBetweenAndUsernameContainingAndEmailIsNotNull(int min, int max, String keyword);

方法名的构成公式详解

Spring Data JPA 的方法命名遵循以下通用公式:

<查询关键词><By><字段条件表达式><附加条件>

结构拆解说明:

组成部分 描述说明 示例
查询关键词 必须以 find、read、get、query 等开头 find、get、count、exists
By 固定关键词,表示“通过什么字段查询” 必须写 By
字段条件表达式 由实体字段 + 条件关键词 + 连接词组合构成 AgeBetweenAndUsernameContaining
附加条件(可选) 如排序、分页(通常配合方法参数) OrderByAgeDesc

条件表达式部分结构

字段条件表达式由多个条件段组成,每段由以下组成:

<字段名><条件关键词>

使用连接词 And / Or 连接多个段:

1
findByAgeBetweenAndUsernameContainingAndEmailIsNotNull
字段 条件关键词 含义
Age Between 年龄在两个值之间
Username Containing 名字包含
Email IsNotNull 邮箱不为空

查询关键词列表(开头部分)

查询关键词 说明
find 查询并返回实体或集合
read 同 find,更偏语义化
get 同上
query 同上
count 返回记录数
exists 返回布尔值是否存在
delete 删除操作

附加条件关键词(排序)

附加关键词 示例 含义
OrderBy findByUsernameOrderByAgeDesc() 根据某字段排序
Asc 升序
Desc 降序

总结规则

findBy + <字段1><条件1> + And + <字段2><条件2> + ... + OrderBy<字段>Asc/Desc

Spring Boot 在启动时,会根据这些命名规范生成底层 SQL 查询或 JPQL 查询。

5. 注意事项

  1. 方法名不能拼错,否则启动报错。
  2. 支持分页、排序:继承 JpaRepository 后可使用 Pageable。
  3. 若方法命名表达不了复杂查询,可以使用 @Query 写 JPQL 或原生 SQL。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# JPQL 语法它操作的是实体对象名和字段名,不是数据库表和列。
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.username = ?2")
User findByEmailAndUsername(String email, String username);

也可以这样写,增强可读性
@Query("SELECT u FROM User u WHERE u.email = :email AND u.username = :username")
User findByEmailAndUsername(@Param("email") String email, @Param("username") String username);

# 原生 SQL 语法它操作的是数据库表名和列名,而不是实体对象名和字段名。
@Query(value = "SELECT * FROM user WHERE email = ?1 AND username = ?2", nativeQuery = true)
User findByEmailAndUsername(String email, String username);

总结

Spring Data JPA 提供了声明式的数据访问方式,结合 JpaRepository 和方法命名规则,我们几乎可以不写 SQL,就能完成日常数据库操作。当你看到 UserRepository 里有 existsByUsername 这样的接口方法时,不用慌,它就是 Spring Data JPA 根据你写的方法名自动实现的查询逻辑!