本文所属【知识林】:http://www.zslin.com/web/article/detail/20

在网站系统开发过程中搜索筛选功能随处可见,在JPA中提供的筛选功能只要简单做下封装将非常好用。在文章《Springboot 之 使用JPA对数据进行排序》《Springboot 之 使用JPA进行分页操作》中讲述了JPA的排序和分页,在本篇文章中将以前两个测试项目的数据做一下筛选功能的描述及详情的测试。

  • 修改接口对象

筛选功能需要继承于JpaSpecificationExecutor接口,修改IUserService如下:

import com.zslin.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * Created by 钟述林 393156105@qq.com on 2016/10/21 17:02.
 */
public interface IUserService extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
}
  • 创建筛选条件DTO对象
public class SearchDto {

    private String key;
    private String operation;
    private Object value;

    public SearchDto(String key, String operation, Object value) {
        this.key = key;
        this.operation = operation;
        this.value = value;
    }

    ……省去get和set方法……
}
  • 封装筛选对象

这步非常关键,内容也相对较多:

package com.zslin.tools;

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;

/**
 * Created by 钟述林 393156105@qq.com on 2016/10/21 17:16.
 */
public class BaseSearch<T> implements Specification<T> {

    public static final String GRATE_EQUAL = "ge"; //大于等于
    public static final String GRATE_THEN = "gt"; //大于
    public static final String LESS_EQUAL = "le"; //小于等于
    public static final String LESS_THEN = "lt"; //小于
    public static final String LIKE_BEGIN = "likeb"; // like '%?'
    public static final String LIKE_END = "likee"; //like '?%'
    public static final String LIKE = "like"; //like '%?%'
    public static final String LIKE_BEGIN_END = "likebe"; //like '%?%'
    public static final String NOT_LIKE_BEGIN = "nlikeb"; //not like '%?'
    public static final String NOT_LIKE_END = "nlikee"; //not like '?%'
    public static final String NOT_LIKE = "nlike"; //not like '%?%'
    public static final String NOT_LIKE_BEGIN_END = "nlikebe"; // not like '%?%'
    public static final String EQUAL = "eq"; //equal =
    public static final String NOT_EQUAL = "neq"; // not equal   !=
    public static final String IS_NULL = "isnull"; //is null

    private SearchDto criteria;
    public BaseSearch(SearchDto criteria) {
        this.criteria = criteria;
    }

    @Override
    public Predicate toPredicate
            (Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        String opt = criteria.getOperation();
        String key = criteria.getKey();
        String value = criteria.getValue().toString();
        if (opt.equalsIgnoreCase(GRATE_EQUAL)) { //大于等于
            return builder.greaterThanOrEqualTo(
                    root.<String> get(key), value);
        } else if(opt.equalsIgnoreCase(GRATE_THEN)) { //大于
            return builder.greaterThan(root.<String> get(key), value);
        } else if(opt.equalsIgnoreCase(LESS_EQUAL)) { //小于等于
            return builder.lessThanOrEqualTo(root.<String>get(key), value);
        } else if(opt.equalsIgnoreCase(LESS_THEN)) { //小于
            return builder.lessThan(root.<String>get(key), value);
        } else if(opt.equalsIgnoreCase(LIKE_BEGIN)) { // like '%?'
            return builder.like(root.<String>get(key), "%"+value);
        } else if(opt.equalsIgnoreCase(LIKE_END)) { // like '?%'
            return builder.like(root.<String>get(key), value+"%");
        } else if(opt.equalsIgnoreCase(LIKE) || opt.equalsIgnoreCase(LIKE_BEGIN_END)) { //like '%?%'
            return builder.like(root.<String>get(key), "%"+value+"%");
        } else if(opt.equalsIgnoreCase(NOT_LIKE_BEGIN)) { // not like '%?'
            return builder.notLike(root.<String>get(key), "%"+value);
        } else if(opt.equalsIgnoreCase(NOT_LIKE_END)) { // not like '?%'
            return builder.notLike(root.<String> get(key), value + "%");
        } else if(opt.equalsIgnoreCase(NOT_LIKE) || opt.equalsIgnoreCase(NOT_LIKE_BEGIN_END)) { //not like '%?%'
            return builder.notLike(root.<String> get(key), "%"+value+"%");
        } else if(opt.equalsIgnoreCase(EQUAL)) { //equal
            return builder.equal(root.get(key), value);
        } else if(opt.equalsIgnoreCase(NOT_EQUAL)) { //not equal
            return builder.notEqual(root.get(key), value);
        } else if(opt.equalsIgnoreCase(IS_NULL)) { // is null
            return builder.isNull(root.get(key));
        }
        return null;
    }
}

通过这个封装的筛选对象基本可以满足我们大部份的筛选需求。

  • 测试
private void print(List<User> list) {
    for(User u : list) {
        System.out.println(u.getId()+"==="+u.getUserName());
    }
}

@Test
public void test1() {
    List<User> list = userService.findAll(new BaseSearch<User>(new SearchDto("userName","eq", "user1")));
    print(list);
}

上面这个例子是测试筛选 userName等于user1的数据,是属于单一条件的筛选。

  • 多条件筛选

多条件筛选相对较复杂,需要修改和增加以下代码:

为SearchDto增加属性

public class SearchDto {

    /** 拼接类型,and或者or */
    private String type;
    private String key;
    private String operation;
    private Object value;

    public SearchDto(String key, String operation, Object value) {
        this.key = key;
        this.operation = operation;
        this.value = value;
    }

    public SearchDto(String type, String key, String operation, Object value) {
        this.type = type;
        this.key = key;
        this.operation = operation;
        this.value = value;
    }

    ……省去get和set方法……
}

增加筛选条件的DTO对象

public class SearchSpeDto {

    /** 类型,and或者or */
    private String type;

    private Specifications spes;

    public SearchSpeDto(String type, Specifications spes) {
        this.type = type;
        this.spes = spes;
    }

    public Specifications getSpes() {
        return spes;
    }

    public String getType() {
        return type;
    }

    public void setSpes(Specifications spes) {
        this.spes = spes;
    }

    public void setType(String type) {
        this.type = type;
    }
}

创建筛选功能对象

public class SearchTools {

    public static Specification buildSpecification(SearchSpeDto... speDtos) {
        Specifications result = null;
        for(SearchSpeDto dto : speDtos) {
            if(result==null) {
                result = Specifications.where(dto.getSpes());
            } else {
                if("and".equalsIgnoreCase(dto.getType())) {
                    result = result.and(dto.getSpes());
                } else {
                    result = result.or(dto.getSpes());
                }
            }
        }
        return result;
    }

    public static SearchSpeDto buildSpeDto(String type, SearchDto... searchDtos) {
        SearchSpeDto speDtos = null;

        Specifications result = null;
        for(SearchDto dto : searchDtos) {
            if(result==null) {
                result = Specifications.where(new BaseSearch(dto));
            } else {
                if("and".equalsIgnoreCase(dto.getType())) {
                    result = result.and(new BaseSearch(dto));
                } else {
                    result = result.or(new BaseSearch(dto));
                }
            }
        }
        speDtos = new SearchSpeDto(type, result);
        return speDtos;
    }
}

测试代码

 @Test
public void test2() {
    List<User> list = userService.findAll(SearchTools.buildSpecification(
            SearchTools.buildSpeDto("and", new SearchDto("and", "id", "gt", 2)),
            SearchTools.buildSpeDto("and", new SearchDto("userName", "ne", "user5"),
                    new SearchDto("or", "userName", "ne", "user9"))
    ));
    print(list);
}

注意:通过这个例子已经可以传任意条件进行筛选。

示例代码:https://github.com/zsl131/spring-boot-test/tree/master/study10

本文所属【知识林】:http://www.zslin.com/web/article/detail/20
文章点评共:7 条
点评内容: 提交点评

12-22 点评:说实话博主的内容比较贴近初入行的Java程序猿了,对于初级程序猿企业级的开发技术要点无非就是数据的处理,其实楼上有点猴子说得对,博主可以考虑一下写一篇练级操作以及处理关系表之间的注意事项,我经常拿您的文章教育学生,见笑,在这里道声谢了。

08-11 点评:前端有可以直接拿来配合使用的组件么?

03-06 点评:筛选和sql条件又啥区别。

03-08 回复:原理上没啥区别,筛选只是不需要单独写Sql而已

01-03 点评:如果博主能添加一些关于 jpa的多表之间的连查 级联操作那些就好了 一些注解方面我还 是不太理解 如果有学习就好了 谢谢博主的分享

01-03 回复:这个建议不错!

01-03 点评:一直在关注着 博主 你真是帮我大忙 了 在这里学到了很多东西 希望博主再造辉煌

01-03 回复:好的!只要有新的总结我都会及时放上来的~~能带来些帮助,解决些问题,我感到很高兴!

12-20 点评:首先感受下作者哈,然后点出一处小疑问; SearchTools 中的buildSpecification方法的入参是SearchSpeDto类型的,但是在测试方法中调用的时候,最后一个参数是SearchDto,额,可能看得不清晰,再对比一般,SearchSpeDto不等于SearchDto

12-23 回复:单元测试和SearchTools中有点区别,主要是在SearchTools中要与HttpRequest有关联

12-06 点评:写的不错

12-07 回复:谢谢!