1. 前言

有时我们在查询某个实体的时候,给定的条件是不固定,这个时候就需要动态构建相应的查询语句,在 Spring Data JPA 中可以通过继承 JpaSpecificationExecutor 接口来实现动态查询。相比于使用 JPQL 来进行动态查询,其优势是类型安全,更加的面向对象。

DAO 层需要继承 JpaSpecificationExecutor 接口

1
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User>{}

项目代码:https://github.com/ShangguanHong/DemoSpringBoot/tree/master/springboot-jpa

2. 分析 JpaSpecificationExecutor 接口

JpaSpecificationExecutor 接口内的方法有以下几个,我们可以看到方法中都接受Specification(查询条件) 作为参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public interface JpaSpecificationExecutor<T> { 
    // 根据条件查询单个对象
    Optional<T> findOne(@Nullable Specification<T> var1);
	// 根据条件查询列表
    List<T> findAll(@Nullable Specification<T> var1);
	// 根据条件查询列表并分页
    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
	// 根据条件查询列表并排序
    List<T> findAll(@Nullable Specification<T> var1, Sort var2);
	// 满足条件的数量
    long count(@Nullable Specification<T> var1);
}

Specification<T> :查询条件

Pageable :分页参数

Sort :排序参数

3. 分析 Specification 接口

打开 Specification 接口可以看到里面就定义了一个方法

1
2
3
4
5
// root:查询的根对象,查询的任何属性都可以从根对象中获取(类似 SQL 语句 from 后面的表)
// query:顶层查询对象,自定义查询方式(一般不用到)
// cb:查询的构造器,封装了很多的查询条件(精确查询,模糊查询等)
// 比较的属性在 root 中,比较的方式在 cb 中
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

由于 Specification 是一个接口,因此 我们想自定义查询条件,就需要自定义一个我们自己的 Specification 实现类

4. 使用示例

  1. 精确查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    // 查询用户名为root的用户
    @Test
    @Transactional
    public void SpecTest1() {
        // lambda 表达式
        User user = userRepository.findOne((root, query, cb) -> {
            // 比较方式为精确查询:cb.equal
            // 比较的属性为用户名:root.get("userName")
            // 比较的类型:as(String.class)
            Predicate predicate = cb.equal(root.get("userName").as(String.class), "root");
            return predicate;
        }).get();
        System.out.println(user);
    }

1563790233042

  1. 模糊查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
	// 查询用户名为test开头的用户
    @Test
    @Transactional
    public void SpecTest2() {
        // lambda 表达式
        List<User> users = userRepository.findAll((root, query, cb) -> {
            // 比较方式为模糊查询:cb.like
            // 比较的属性为用户名:root.get("userName")
            // 比较的类型:as(String.class)
            Predicate predicate = cb.like(root.get("userName").as(String.class), "test%");
            return predicate;
        });
        for (User user : users) {
            System.out.println(user);
        }
    }

1563790782009

  1. 多条件查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    // 查询用户名为test开头并且地址为shanghai的用户
    @Test
    @Transactional
    public void SpecTest3() {
        // lambda 表达式
        List<User> users = userRepository.findAll((root, query, cb) -> {
            // 比较方式为模糊查询:cb.like
            // 比较的属性为用户名:root.get("userName")
            // 比较的类型:as(String.class)
            List<Predicate> list = new ArrayList<>();
            list.add(cb.like(root.get("userName").as(String.class), "test%"));
            // 比较方式为精确查询:cb.equal
            // 比较的属性为地址:root.get("userAddr")
            // 比较的类型:as(String.class)
            list.add(cb.equal(root.get("userAddr").as(String.class), "shanghai"));
            Predicate[] p = new Predicate[list.size()];
            // cb.and : 所有条件的与(或用or)
            return cb.and(list.toArray(p));
        });
        for (User user : users) {
            System.out.println(user);
        }
    }

1563792083275

  1. 排序查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    // 查询所有用户,并以用户ID倒序
    @Test
    @Transactional
    public void SpecTest4() {
        // 创建排序对象,需要调用构造方法实例化sort对象
        // 第一个参数:排序的顺序(Sort.Direction.ASC:正序,Sort.Direction.DESC:逆序)
        // 第二个参数:排序的属性名称
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        // lambda 表达式
        // 排序查询
        List<User> users = userRepository.findAll((root, query, cb) -> {
            // 不做条件筛选
            return null;
        }, sort);
        for (User user : users) {
            System.out.println(user);
        }
    }

1563792658039

  1. 分页查询
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    // 查询所有用户,并进行分页
    @Test
    @Transactional
    public void SpecTest5() {
        // 创建分页对象,PageRequest对象是Pageable接口的实现类
        // 调用PageRequest的构造函数实例化一个PageRequest对象
        // 第一个参数:当前查询的页数(从0开始)
        // 第二个参数:每页的数量
        Pageable pageable = new PageRequest(0, 1);
        // lambda 表达式
        // 分页查询
        Page<User> userPage = userRepository.findAll((root, query, cb) -> {
            // 不做条件筛选
            return null;
        }, pageable);
        List<User> users = userPage.getContent();
        System.out.println(users);
        System.out.println("用户总数量为:" + userPage.getTotalElements());
        System.out.println("总页数为:" + userPage.getTotalPages());
    }

1563793110699