1. 概述

Spring Data JPA 提供了一个查询推导功能,允许开发者通过遵循方法名称约定,自动衍生查询语句。

本文将利用这个特性,通过单个或多个列查找实体。

2. 示例设置

以一个包含用户账户相关属性的 Account 实体为例:

@Entity
@Table(name = "ACCOUNTS")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "accounts_seq")
    @SequenceGenerator(name = "accounts_seq", sequenceName = "accounts_seq", allocationSize = 1)
    @Column(name = "user_id")
    private int userId;
    private String username;
    private String password;
    private String email;
    private Timestamp createdOn;
    private Timestamp lastLogin;

    @OneToOne
    @JoinColumn(name = "permissions_id")
    private Permission permission;
    
    // getters and setters
}

为了演示,我们将在 Accounts 表中添加一些示例数据:

userId username password email createdOn lastLogin permission
1 user_admin 737d4251-7ccf-46ce-8227-24cce9be812e [email protected] 2024-02-08 21:26:30.372286 2024-02-09 21:26:30.37229 editor
2 user_admin_1 65cfd915-240c-4f64-8378-27fa1ff9cdf5 [email protected] 2024-02-06 21:26:30.372286 2024-02-07 21:26:30.37229 editor
3 user_admin_2 9b4dca2e-f1d2-4b14-9553-3b8913323b48 [email protected] 2024-02-04 21:26:30.372286 2024-02-06 21:26:30.37229 editor

3. 查询推导

查询推导允许开发者在仓库接口中定义遵循特定命名约定的方法,框架会根据方法名自动生成相应的查询。

例如,如果我们想通过电子邮件地址搜索 accounts 表,AccountRepository 中的方法如下:

public interface AccountRepository extends JpaRepository<Account, Integer> {
    Account findByEmail(String email);
}

当我们在 AccountRepository 上执行 findByEmail() 时,Spring Data 会生成以下 SQL 并执行到 accounts 表:

select a1_0.user_id,a1_0.created_on,a1_0.email,a1_0.last_login,a1_0.password,a1_0.permissions_id,a1_0.username
from accounts a1_0 
where a1_0.email=?

我们的测试验证了 findByEmail() 的工作:

@Test
void givenAccountInDb_whenPerformFindByEmail_thenReturnsAccount() {
    String email = "[email protected]";
    Account account = accountRepository.findByEmail(email);
    assertThat(account.getEmail()).isEqualTo(email);
}

4. 多列查询 (findBy*()) 方法

我们可以扩展查询推导功能,添加组合条件来获取合适的结果。

使用 AccountRepository 接口,我们再编写一个方法,用于根据用户名和电子邮件查找 Account

public interface AccountRepository extends JpaRepository<Account, Integer> {
    Account findByUsernameAndEmail(String username, String email);
}

定义的方法生成的 SQL 如下:

select a1_0.user_id,a1_0.created_on,a1_0.email,a1_0.last_login,a1_0.password,a1_0.permissions_id,a1_0.username
from accounts a1_0
where a1_0.username=? and a1_0.email=?

测试确认了 findByUsernameAndEmail() 的工作:

@Test
void givenAccountInDb_whenPerformFindByUsernameAndEmail_thenReturnsAccount(){
    String email = "[email protected]";
    String username = "user_admin";
    Account account = accountRepository.findByUsernameAndEmail(username, email);
    assertThat(account.getUsername()).isEqualTo(username);
    assertThat(account.getEmail()).isEqualTo(email);
}

我们也可以使用 OR 运算符组合两个条件。例如,可以通过用户名或电子邮件进行搜索:

public interface AccountRepository extends JpaRepository<Account, Integer> {
    Account findByUsernameOrEmail(String username, String email);
}

对应的生成的 SQL:

select a1_0.user_id,a1_0.created_on,a1_0.email,a1_0.last_login,a1_0.password,a1_0.permissions_id,a1_0.username
from accounts a1_0
where a1_0.username=? or a1_0.email=?

现在,让我们验证 findByUsernameOrEmail() 的工作:

@Test
void givenAccountInDb_whenPerformFindByUsernameOrEmail_thenReturnsAccount(){
    String email = "[email protected]";
    String username = "user_editor";
    Account account = accountRepository.findByUsernameOrEmail(username, email);
    assertThat(account.getUsername()).isNotEqualTo(username);
    assertThat(account.getEmail()).isEqualTo(email);
}

我们还可以在 findBy() 方法中使用集合作为输入。例如,查找存在于电子邮件列表或用户名列表中的所有账户:

public interface AccountRepository extends JpaRepository<Account, Integer> {
    List<Account> findByUsernameInOrEmailIn(List<String> usernames, List<String> emails);
}

生成的 SQL:

select a1_0.user_id,a1_0.created_on,a1_0.email,a1_0.last_login,a1_0.password,a1_0.permissions_id,a1_0.username 
from accounts a1_0 
where a1_0.username in (?,?) or a1_0.email in (?,?,?)

测试确认了 findByUsernameInOrEmailIn() 的工作:

@Test
void givenAccountInDb_whenPerformFindByUsernameInOrEmailIn_thenReturnsAccounts(){
    List<String> emails = List.of("[email protected]", "[email protected]", "[email protected]");
    List<String> usernames = List.of("user_editor", "user_admin");
    List<Account> byUsernameInOrEmailIn = accountRepository.findByUsernameInOrEmailIn(usernames, emails);
    assertThat(byUsernameInOrEmailIn.size()).isEqualTo(1);
    assertThat(byUsernameInOrEmailIn.get(0).getEmail()).isEqualTo("[email protected]");
}

5. 总结

本教程讨论了 Spring Data 的查询推导功能,并用它来在表中查找实体。我们还探讨了如何使用不同的输入参数,根据 ANDOR 条件找到实体。

如往常一样,示例代码可在 GitHub 查看。