SpringBoot第五讲扩展和封装Spring Data JPA(二)_利用Specification封装简单查询

上一讲讲解了如何使用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 implements Specification {

    /**
     * 查询的条件列表,是一组列表
     * */
    private List opers;

    public SimpleSpecification(List opers) {
        this.opers = opers;
    }

    @Override
    public Predicate toPredicate(Root 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 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 {

    /**
     * 条件列表
     */
    private List 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();
        opers.add(so);
    }

    public SimpleSpecificationBuilder() {
        opers = new ArrayList();
    }

    /**
     * 完成条件的添加
     * @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 specification = new SimpleSpecification(opers);
        return specification;
    }
}

现在几个比较重要的类以及实现,接下来看看具体的调用。首先创建一个StudentRepository的接口实现JpaSpecificationExecutor


/**
 * Created by konghao on 2016/12/16.
 * 该接口实现了上一节介绍的BaseRepository和JpaSpecificationExecutor
 * JpaSpecificationExecutor可以通过findAll方法传入SimpleSpecification来进行查询
 */
public interface StudentRepository extends BaseRepository,JpaSpecificationExecutor {

}

大家注意这个接口中没有任何的查询,基本就是一个空接口,按照原来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 stus = studentRepository.findAll(
                new SimpleSpecificationBuilder("id",">",4)
                        .addOr("name",":","a")
                        .generateSpecification());

        Assert.assertEquals(5,stus.size());

    }
}

我们完成了一个不算太复杂的查询,如果你原来认为在接口中写衍生查询的方法太复杂,用现在这种方式是不是简单了许多呢?好了,这一讲就到这里,下一讲我们可以结束Spring Data JPA了。就差两个非常简单的小知识:分页和事务处理。、
本文的源代码在这里:源代码

你可能感兴趣的:(SpringBoot第五讲扩展和封装Spring Data JPA(二)_利用Specification封装简单查询)