Spring Data JPA进阶(三):Specification查询

x33g5p2x  于2021-12-19 转载在 其他  
字(2.6k)|赞(0)|评价(0)|浏览(453)

Spring Data JPA支持非常丰富的查询方式,本文主要介绍Specification查询。

JPA 2 规范引进了criteria查询API。Spring Data JPA对此提供了支持。如果你想使用这个功能,只需要继承JpaSpecificationExecutor接口。这个接口已经实现了基本的查询方法(findOne,findAll,count等)。

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

JpaSpecificationExecutor主要用到了Specification,它是一个接口,有一个实现类叫Specifications。这个接口主要定义了下面这个方法,在加上一些not, where, and, or等default或static的逻辑方法

public interface Specification<T> {
    
  @Nullable
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

  static <T> Specification<T> not(Specification<T> spec) {
    return Specifications.negated(spec);
  }

  static <T> Specification<T> where(Specification<T> spec) {
    return Specifications.where(spec);
  }

  default Specification<T> and(Specification<T> other) {
    return Specifications.composed(this, other, AND);
  }

  default Specification<T> or(Specification<T> other) {
    return Specifications.composed(this, other, OR);
  }
}

可以看到它其实是一个函数式接口。最重要的是那个抽象方法toPredicate。按照官方的例子,推荐我们使用static方法来生产Specification对象:

public class UserSpecs {
    public static Specification<User> ageEqual(int age) {
        return (root, query, builder) -> builder.equal(root.get("age"), age);
    }
    
	 public static Specification<User> nameLike(String pattern) {
        return (r, q, b) -> b.like(r.get("name"), pattern);
    }
}

然后我们就可以使用它了。测试用例:

@Test
void couldGetUser_givenAge() {
    Optional<User> find = userRepository.findOne(ageEqual(24));
    assertThat(find.isPresent()).isTrue();
    find.ifPresent(x -> assertThat(x.getName()).isEqualTo("Michael"));
}

@Test
void couldGetUser_givenNamePattern() {
    Optional<User> find = userRepository.findOne(nameLike("Mi%").and(ageEqual(24)));
    assertThat(find.isPresent()).isTrue();
    find.ifPresent(x -> assertThat(x.getName()).isEqualTo("Michael"));
}

你也可以在使用的时候现场造一个:

@Test
void couldGetUser_givenCustomSpecification() {
    Optional<User> find = userRepository.findOne(((root, query, criteriaBuilder) ->
        criteriaBuilder.and(criteriaBuilder.like(root.get("name"), "Mi%"),
                criteriaBuilder.equal(root.get("age"), 24),
                criteriaBuilder.isFalse(root.get("locked")))
    ));

    assertThat(find.isPresent()).isTrue();
    find.ifPresent(x -> assertThat(x.getName()).isEqualTo("Michael"));
}

root、query、criteriaBuild三个参数的类型都是在javax.persistence.criteria包下,且都是在JPA 2.0规范添加的类和接口。这里分别介绍一下三个重要的参数。

root

Root接口,主要用于处理实体和字段、实体与实体之间的关系。除了上述例子中的取字段的操作以外,还可以做join操作。

query

CriteriaQuery接口,主要用于对查询结果的处理,包括groupBy、orderBy、having、distinct等操作。

criteriaBuilder

CriteriaBuilder接口,主要用于各种条件查询、模拟sql函数等。

Specification查询主要用于复杂查询。通过三个参数的组合,可以实现基本上绝大部分sql能实现的复杂查询,且基于Java代码,具有很好的可读性。

相关文章