上一讲讲解了如何使用Spring Data JPA封装一个自己的BaseRespoistory工厂,这个在实际开发中是非常有必要的,今天我们来进一步封装Spring Data JPA的查询。
Spring Data JPA已经帮助我们很大程度上简化了我们的查询操作,我们甚至只要写一个接口,然后单纯的写一些方法就可以完成各式各样的查询,但是对于我们程序设计人员而言,总希望所有的查询变得更加的简单方便,为了给程序人员进行再一次的封装,Spring Data JPA提供了Specification的方式进行查询,在前面的内容已经演示过这种查询了,但是,我们在使用的过程中发现这种查询异常的繁琐和复杂,接下来的内容就是我们有效的对Specification进行封装来快速实现一些简单的查询操作。当然如果涉及到更为复杂的操作,依然建议写个方法来自己实现。
封装自己的Specification的实现有很多种方法,我这里只给出了相对简单的一种,而且并没有考虑太复杂的查询,个人感觉过于复杂的查询还不如直接使用SQL或者HQL来处理方便,以下是几个比较重要的类
/**
* Created by konghao on 2016/12/15.
* 操作符类,这个类中存储了键值对和操作符号,另外存储了连接下一个条件的类型是and还是or
* 创建时通过 id>=7,其中id就是key,>=就是oper操作符,7就是value
* 特殊的自定义几个操作符(:表示like %v%,b:表示v%,:b表示%v)
*/
public class SpecificationOperator {
/**
* 操作符的key,如查询时的name,id之类
*/
private String key;
/**
* 操作符的value,具体要查询的值
*/
private Object value;
/**
* 操作符,自己定义的一组操作符,用来方便查询
*/
private String oper;
/**
* 连接的方式:and或者or
*/
private String join;
...../*省略了getter和setter*/
}
SpecificationOperator
表示操作符类,用来确定查询条件和值。
接下来创建SimpleSpecification
来实现Specification
接口,并且根据条件生成Specification
对象,因为在最后查询的时候需要这个对象
package org.konghao.specification;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;
/**
* Created by konghao on 2016/12/15.
*/
public class SimpleSpecification<T> implements Specification<T> {
/**
* 查询的条件列表,是一组列表
* */
private List<SpecificationOperator> opers;
public SimpleSpecification(List<SpecificationOperator> opers) {
this.opers = opers;
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
int index = 0;
//通过resultPre来组合多个条件
Predicate resultPre = null;
for(SpecificationOperator op:opers) {
if(index++==0) {
resultPre = generatePredicate(root,criteriaBuilder,op);
continue;
}
Predicate pre = generatePredicate(root,criteriaBuilder,op);
if(pre==null) continue;
if("and".equalsIgnoreCase(op.getJoin())) {
resultPre = criteriaBuilder.and(resultPre,pre);
} else if("or".equalsIgnoreCase(op.getJoin())) {
resultPre = criteriaBuilder.or(resultPre,pre);
}
}
return resultPre;
}
private Predicate generatePredicate(Root<T> root,CriteriaBuilder criteriaBuilder, SpecificationOperator op) {
/*
* 根据不同的操作符返回特定的查询*/
if("=".equalsIgnoreCase(op.getOper())) {
System.out.println(op.getKey()+","+op.getValue());
return criteriaBuilder.equal(root.get(op.getKey()),op.getValue());
} else if(">=".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.ge(root.get(op.getKey()), (Number)op.getValue());
} else if("<=".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.le(root.get(op.getKey()),(Number)op.getValue());
} else if(">".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.gt(root.get(op.getKey()),(Number)op.getValue());
} else if("<".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.lt(root.get(op.getKey()),(Number)op.getValue());
} else if(":".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.like(root.get(op.getKey()),"%"+op.getValue()+"%");
} else if("l:".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.like(root.get(op.getKey()),op.getValue()+"%");
} else if(":l".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.like(root.get(op.getKey()),"%"+op.getValue());
} else if("null".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.isNull(root.get(op.getKey()));
} else if("!null".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.isNotNull(root.get(op.getKey()));
} else if("!=".equalsIgnoreCase(op.getOper())) {
return criteriaBuilder.notEqual(root.get(op.getKey()),op.getValue());
}
return null;
}
}
SimpleSpecification
是核心类型,用来根据条件生成Specification对象,这个SimpleSpecification
直接存储了具体的查询条件。
最后我们创建一个SimpleSpecificationBuilder
来具体创建SimpleSpecification
,这里为了方便调用简单进行了一下设计。
package org.konghao.specification;
import org.springframework.data.jpa.domain.Specification;
import java.util.ArrayList;
import java.util.List;
/**
* Created by konghao on 2016/12/15.
*/
public class SimpleSpecificationBuilder<T> {
/**
* 条件列表
*/
private List<SpecificationOperator> opers;
/**
* 构造函数,初始化的条件是and
*/
public SimpleSpecificationBuilder(String key,String oper,Object value) {
SpecificationOperator so = new SpecificationOperator();
so.setJoin("and");
so.setKey(key);
so.setOper(oper);
so.setValue(value);
opers = new ArrayList<SpecificationOperator>();
opers.add(so);
}
public SimpleSpecificationBuilder() {
opers = new ArrayList<SpecificationOperator>();
}
/**
* 完成条件的添加
* @return
*/
public SimpleSpecificationBuilder add(String key,String oper,Object value,String join) {
SpecificationOperator so = new SpecificationOperator();
so.setKey(key);
so.setValue(value);
so.setOper(oper);
so.setJoin(join);
opers.add(so);
return this;
}
/**
* 添加or条件的重载
* @return this,方便后续的链式调用
*/
public SimpleSpecificationBuilder addOr(String key,String oper,Object value) {
return this.add(key,oper,value,"or");
}
/**
* 添加and的条件
* @return
*/
public SimpleSpecificationBuilder add(String key,String oper,Object value) {
return this.add(key,oper,value,"and");
}
public Specification generateSpecification() {
Specification<T> specification = new SimpleSpecification<T>(opers);
return specification;
}
}
现在几个比较重要的类以及实现,接下来看看具体的调用。首先创建一个StudentRepository
的接口实现JpaSpecificationExecutor
/**
* Created by konghao on 2016/12/16.
* 该接口实现了上一节介绍的BaseRepository和JpaSpecificationExecutor
* JpaSpecificationExecutor可以通过findAll方法传入SimpleSpecification来进行查询
*/
public interface StudentRepository extends BaseRepository<Student,Integer>,JpaSpecificationExecutor<Student> {
}
大家注意这个接口中没有任何的查询,基本就是一个空接口,按照原来JPA的处理方式,我们需要为一些简单的查询写一些类似findByUsername
的方法。现在我们已经封装了自己的SimpleSpecification
,我们来看看测试类如何调用。
package org.konghao;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.konghao.model.Student;
import org.konghao.repository.StudentRepository;
import org.konghao.specification.SimpleSpecificationBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
private StudentRepository studentRepository;
@Test
public void testFind() {
/**
* 这里的查询表示id大于4或者name中包含a
* 现在我们发现在SimpleSpecificationBuilder的add或者addOr方法中返回this的好处了
*/
List<Student> stus = studentRepository.findAll(
new SimpleSpecificationBuilder("id",">",4)
.addOr("name",":","a")
.generateSpecification());
Assert.assertEquals(5,stus.size());
}
}
我们完成了一个不算太复杂的查询,如果你原来认为在接口中写衍生查询的方法太复杂,用现在这种方式是不是简单了许多呢?好了,这一讲就到这里,下一讲我们可以结束Spring Data JPA了。就差两个非常简单的小知识:分页和事务处理。、
本文的源代码在这里:源代码