方法名定义查询方法
Spring Data JPA 的最大特色是利用方法名定义查询方法(Defining Query Methods)来做 CRUD 操作。DQM 语法共有 2 种,可以实现上面的那些问题,具体如下:
一种是直接通过方法名就可以实现
另一种是 @Query 手动在方法上定义
定义查询方法的配置和使用方法
若想要实现 CRUD 的操作,常规做法是写一大堆 SQL 语句。但在 JPA 里面,只需要继承 Spring Data Common 里面的任意 Repository 接口或者子接口,然后直接通过方法名就可以实现,神不神奇?来看下面具体的使用步骤。
第 1 步,User 实体的 UserRepository 继承 Spring Data Common 里面的 Repository 接口:
interface UserRepository extends CrudRepository<User, Long> {
User findByEmailAddress(String emailAddress);
}
第 2 步,对于 Service 层就可以直接使用 UserRepository 接口:
@Service
public class UserServiceImpl{
@Autowired
UserRepository userRepository;
public void testJpa() {
userRepository.deleteAll();
userRepository.findAll();
userRepository.findByEmailAddress("abc@126.com");
}
这个时候就可以直接调用 CrudRepository 里面暴露的所有接口方法,以及 UserRepository 里面定义的方法,不需要写任何 SQL 语句,也不需要写任何实现方法。通过上面的两步我们完成了 Defining Query Methods(DQM)的基本使用。
有时如果不想暴露 CrudRepository 里面的所有方法,那么可以直接继承我们认为需要暴露的那些方法的接口。假如 UserRepository 只想暴露 findOne 和 save,除了这两个方法之外不允许任何的 User 操作,我们选择性地暴露 CRUD 方法,直接继承Repository(因为这里面没有任何方法),把CrudRepository 里面的 save 和 findOne 方法复制到我们自己的 MyBaseRepository 接口即可,代码如下:
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
T save(T entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(String emailAddress);
}
这样在 Service 层就只有 findOne、save、findByEmailAddress 这 3 个方法可以调用,不会有更多方法了,我们可以对 SimpleJpaRepository 里面任意已经实现的方法做选择性暴露。
综上所述,得出以下 2 点结论:
MyRepository Extends Repository 接口可以实现 Defining Query Methods 的功能;
继承其他 Repository 的子接口,或者自定义子接口,可以选择性地暴露 SimpleJpaRepository 里面已经实现的基础公用方法。
方法的查询策略设置
可以通过方法名,或者定义方法名上面添加 @Query 注解两种方式来实现 CRUD 的目的,而 Spring 给我们提供了两种切换方式。
通过 @EnableJpaRepositories 注解来配置方法的查询策略,详细配置方法如下:
@EnableJpaRepositories(queryLookupStrategy= QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)
其中,QueryLookupStrategy.Key 的值共 3 个,具体如下:
Create:直接根据方法名进行创建,规则是根据方法名称的构造进行尝试,一般的方法是从方法名中删除给定的一组已知前缀,并解析该方法的其余部分。如果方法名不符合规则,启动的时候会报异常,这种情况可以理解为,即使配置了 @Query 也是没有用的。
USE_DECLARED_QUERY:声明方式创建,启动的时候会尝试找到一个声明的查询,如果没有找到将抛出一个异常,可以理解为必须配置 @Query。
CREATE_IF_NOT_FOUND:这个是默认的,除非有特殊需求,可以理解为这是以上 2 种方式的兼容版。先用声明方式(@Query)进行查找,如果没有找到与方法相匹配的查询,那用 Create 的方法名创建规则创建一个查询;这两者都不满足的情况下,启动就会报错。
在Spring Boot 项目中,配置如下:
@EnableJpaRepositories(queryLookupStrategy= QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND)
public class Example1Application {
public static void main(String[] args) {
SpringApplication.run(Example1Application.class, args);
}
}
Defining Query Method(DQM)语法
该语法是:带查询功能的方法名由查询策略(关键字)+ 查询字段 + 一些限制性条件组成,具有语义清晰、功能完整的特性,我们实际工作中 80% 的 API 查询都可以简单实现。
我们来看一个复杂点的例子,这是一个 and 条件更多、distinct or 排序、忽略大小写的例子。下面代码定义了 PersonRepository,我们可以在 service 层直接使用,如下所示:
interface PersonRepository extends Repository<User, Long> {
// and 的查询关系
List<User> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// 包含 distinct 去重,or 的 sql 语法
List<User> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
// 根据 lastname 字段查询忽略大小写
List<User> findByLastnameIgnoreCase(String lastname);
// 根据 lastname 和 firstname 查询 equal 并且忽略大小写
List<User> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// 对查询结果根据 lastname 排序,正序
List<User> findByLastnameOrderByFirstnameAsc(String lastname);
// 对查询结果根据 lastname 排序,倒序
List<User> findByLastnameOrderByFirstnameDesc(String lastname);
}
下面表格是一个我们在上面 DQM 方法语法里常用的关键字列表,方便你快速查阅,并满足在实际代码中更加复杂的场景:
综上,总结 3 点经验:
方法名的表达式通常是实体属性连接运算符的组合,如 And、or、Between、LessThan、GreaterThan、Like 等属性连接运算表达式,不同的数据库(NoSQL、MySQL)可能产生的效果不一样,如果遇到问题,我们可以打开 SQL 日志观察。
IgnoreCase 可以针对单个属性(如 findByLastnameIgnoreCase(…)),也可以针对查询条件里面所有的实体属性忽略大小写(所有属性必须在 String 情况下,如 findByLastnameAndFirstnameAllIgnoreCase(…))。
OrderBy 可以在某些属性的排序上提供方向(Asc 或 Desc),称为静态排序,也可以通过一个方便的参数 Sort 实现指定字段的动态排序的查询方法(如 repository.findAll(Sort.by(Sort.Direction.ASC, “myField”)))。
上面的表格虽然大多是 find 开头的方法,除此之外,JPA 还支持read、get、query、stream、count、exists、delete、remove等前缀,如字面意思一样。我们来看看 count、delete、remove 的例子,其他前缀可以举一反三。实例代码如下:
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);//查询总数
long deleteByLastname(String lastname);//根据一个字段进行删除操作,并返回删除行数
List<User> removeByLastname(String lastname);//根据Lastname删除一堆User,并返回删除的User
}
带查询功能的方法名支持的函数可以到org.springframework.data.repository.query.parser.PartTree源码去查看,方法中的关键字也不是乱填的,是枚举帮我们定义好的。可以到org.springframework.data.repository.query.parser.Part.Type下查看源码~