Spring Data JPA 进阶:优雅实现 mybatis
2018-12-10 本文已影响45人
黄大海
- JPA和Mybatis相比还是比较复杂的,Spring Data JPA极大的简化了JPA的处理。但是在处理复杂查询方面比较繁琐。
- 用惯Mybais的人转到Spring Data JPA可能最大的抱怨就是处理IF/ELSE条件。只有条件非空时才加入条件。
这里介绍下mybatis style 的查询的几种方法
-
QBE(QueryByExample)
- 这个可以处理等值比较和like查询,所有非空字段都会加入条件
Book example = new Book(); example.setColor("Black") example.setSize("Big") bookRepository.findAll(Example.of(example)); //select * from BOOK where color = 'Black' and size = 'Big'
-
QBE不能处理NOT,IN, BETWEEN,<, > 这些条件。官方提供的方案叫Specification
- 大概是这个感觉:
public class BookSpecifications { public static Specification<Book> sizeNot(final String size){ return new Specification<Book>(){ @Override public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.not(root.<String>get("size"), size); } }; } public static Specification<Book> categoryIn(final List<String> categories){ return new Specification<Book>(){ @Override public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return root.<String>get("category")).in(categories); } }; } } ... Specification spec = Specifications .where(BookSpecifications.sizeNot("small")) .and(BookSpecifications.categoryIn(...)); bookRepository.findAll(spec);
- JDK1.8后,我们可以用lambda来简化
Specification spec = Specifications .<Book>where((r,q,c) -> c.not(r.<String>get("size"), size)) .and((r,q,c) -> r.<String>get("category")).in(categories)); bookRepository.findAll(spec);
- 这里还没有空值判断。Specification动态拼装很不方便,因为第一个用的是where然后才是and,需要额外的处理。这里可以用CriteriaBuilder#conjunction()来处理,该条件输出一个 1=1 条件
Specification spec = Specifications .<Book>where((r,q,c) -> StringUtils.isEmpty() ? c.not(r.<String>get("size"), size) : c.conjunction()) .and((r,q,c) -> categories.isEmpty() ? c.conjunction() : r.<String>get("category")).in(categories)); bookRepository.findAll(spec); //当两个条件都为空时:select * from BOOK where 1=1 and 1=1;
-
结合QBE和Specification
Book example = new Book(); example.setColor("Black") example.setSize("Big") Specification spec = Specifications .<Book>where(new ExampleSpecification<>(Example.of(example))) .and((r,q,c) -> StringUtils.isEmpty() ? c.not(r.<String>get("size"), size) : c.conjunction()) .and((r,q,c) -> categories.isEmpty() ? c.conjunction() : r.<String>get("category")).in(categories)); bookRepository.findAll(spec);
- 这里的ExampleSpecification是SimpleJpaRepository. ExampleSpecification,该类是私有的,必须拷贝一份改为public使用。或者可以拷贝其内部代码:
Specifications.where((r,q,c) -> QueryByExamplePredicateBuilder.getPredicate(r, c, Example.of(example)))
- 还可以利用default方法:
public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book>{ public default findByQbeAndSpec(Book example, String sizeNot, List<String> categories){ Specification spec = Specifications .<Book>where(new ExampleSpecification<>(Example.of(example))) .and((r,q,c) -> StringUtils.isEmpty() ? c.not(r.<String>get("size"), size) : c.conjunction()) .and((r,q,c) -> categories.isEmpty() ? c.conjunction() : r.<String>get("category")).in(categories)); return findAll(spec); } }
-
当然也有大神站出来替我们实现了一个工具包,比原版的好用:jpa-spec
public Page<Person> findAll(SearchRequest request) { Specification<Person> specification = Specifications.<Person>and() .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName()) .gt(Objects.nonNull(request.getAge()), "age", 18) .between("birthday", new Date(), new Date()) .like("nickName", "%og%", "%me") .build(); return personRepository.findAll(specification, new PageRequest(0, 15));
}
```