背景

这是个非常实用的视角,掌握 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 提供的核心接口,它继承了 PagingAndSortingRepositoryCrudRepository,提供了大量的 现成方法

方法名 功能
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><字段条件表达式><附加条件>

结构拆解说明:

组成部分 描述说明 示例
查询关键词 必须以 findreadgetquery 等开头 findgetcountexists
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 根据你写的方法名自动实现的查询逻辑!