Spring Boot - JPA 自動拼裝查詢條件,暫時僅限於 and

背景
Spring Boot在使用過程中,經常需要寫拼裝 and 的查詢條件,每次寫的代碼感覺都差不多。因此,仿造前人的例子,寫了一個差不多的東西。

設計思想
約定優於配置。http://blog.csdn.net/zhangzeyuaaa/article/details/43567135

準備階段
Eclipse 需要安裝spring-tool-suite插件,安裝方法:http://blog.csdn.net/woniu211111/article/details/54232260

代碼結構
這裏寫圖片描述

項目具體步驟
1、創建 Maven項目,pom.xml配置如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test.springBoot</groupId>
    <artifactId>autoBuildQuery</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- Spring Boot 項目 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

    <dependencies>

        <!-- 爲Web開發提供支持。 嵌入了Tomcat,提供SpringMVC依賴。 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 代碼的熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!-- JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- apache -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.directory.studio</groupId>
            <artifactId>org.apache.commons.collections</artifactId>
            <version>3.2.1</version>
        </dependency>

        <!-- guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>15.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- springBoot的編譯插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、增加數據庫信息,application.properties配置如下:

spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true

3、增加項目啓動的入口,AutoBuildQueryApplication.java;

package com.test.springBoot.autoBuildQuery;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @SpringBootApplication() 是 Spring Boot 項目的核心註解,主要目的是開啓自動配置。
 *
 */
@SpringBootApplication()
public class AutoBuildQueryApplication {

    /**
     * 標準的 Java 應用的 main 方法,主要作用是作爲項目啓動的入口
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(AutoBuildQueryApplication.class, args);
    }

}

4、創建 entity,Person.java:

package com.test.springBoot.autoBuildQuery.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "T_PERSON")
public class Person {

    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    private Integer age;

    @Column(name = "ID")
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "NAME")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name = "AGE")
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
    }

}

5、DAO,PersonRepository.java:

package com.test.springBoot.autoBuildQuery.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import com.test.springBoot.autoBuildQuery.entity.Person;

@Repository
public interface PersonRepository extends JpaRepository<Person, Integer>, JpaSpecificationExecutor<Person> {
}

6、Service,PersonService.java:

package com.test.springBoot.autoBuildQuery.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import com.test.springBoot.autoBuildQuery.dao.PersonRepository;
import com.test.springBoot.autoBuildQuery.entity.Person;

@Service
public class PersonService {

    @Resource
    private PersonRepository personRepository;

    public List<Person> findListPerson(final Map<String, Object> params) {
        List<Person> list = personRepository.findAll(new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                // build query condition
                List<Predicate> predicates = new ArrayList<Predicate>();
                if (params.get("name_like") != null && StringUtils.isNotBlank((String) params.get("name_like"))) {
                    predicates.add(cb.like(root.get("name").as(String.class), "%" + (String) params.get("name_like") + "%"));
                }
                if (params.get("age_ge_int") != null && StringUtils.isNotBlank((String) params.get("age_ge_int"))) {
                    predicates.add(cb.equal(root.get("age").as(Integer.class), params.get("age_ge_int")));
                }
                query.where(predicates.toArray(new Predicate[predicates.size()]));
                return null;
            }
        });
        return list;
    }

}

7、Controller,PersonController.java:

package com.test.springBoot.autoBuildQuery.web;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.common.collect.Maps;
import com.test.springBoot.autoBuildQuery.entity.Person;
import com.test.springBoot.autoBuildQuery.service.PersonService;

/**
 * 數據接口
 *
 */
@RestController
public class PersonController {

    @Resource
    private PersonService personService;

    /**
     * address : http://localhost:8080/autoBuildQuery
     * @return
     */
    @RequestMapping("/autoBuildQuery")
    public String jpaAutoBuildQuery() {
        Map<String, Object> params = Maps.newHashMap();
        //      params.put("name_like", "張");
        //      params.put("age_ge_int", "2");
        List<Person> people = personService.findListPerson(params);
        System.out.println(people.toString());

        return people.toString();
    }

}

8、數據庫增加數據,並測試能否成功取出所有數據

CREATE
    TABLE T_PERSON 
    (
        ID INT(11) NOT NULL,
        NAME VARCHAR(32) NOT NULL,
        AGE TINYINT(4) NOT NULL 
    )
GO
INSERT INTO T_PERSON(ID, NAME, AGE)
  VALUES(1, '張三', 4)
GO
INSERT INTO T_PERSON(ID, NAME, AGE)
  VALUES(2, '李四', 127)
GO
INSERT INTO T_PERSON(ID, NAME, AGE)
  VALUES(3, '王五', 0)
GO

測試地址:http://localhost:8080/autoBuildQuery

9、引入autoBuildQuery

SearchType.java

package com.test.springBoot.autoBuildQuery.common;

/**
 * 查詢條件
 *
 */
public class SearchType {

    // 查詢條件
    /**
     * 條件連接符 and
     */
    public static final String AND = "and";
    /**
     * 條件匹配符 =
     */
    public static final String EQ = "eq";
    /**
     * 條件匹配符 !=
     */
    public static final String NE = "ne";
    /**
     * 條件匹配符 <
     */
    public static final String LT = "lt";
    /**
     * 條件匹配符 <=
     */
    public static final String LE = "le";
    /**
     * 條件匹配符 >
     */
    public static final String GT = "gt";
    /**
     * 條件匹配符 >=
     */
    public static final String GE = "ge";

    /**
     * 條件匹配符 like %---%
     */
    public static final String LIKE = "like";
    /**
     * 條件匹配符 not like %%
     */
    public static final String NOTLIKE = "notlike";
    /**
     * 條件匹配符 like ---%
     */
    public static final String START = "start";
    /**
     * 條件匹配符 not like ---%
     */
    public static final String NOTSTART = "notstart";
    /**
     * 條件匹配符 in (--,--,--)
     */
    public static final String IN = "in";
    /**
     * 條件匹配符 not in (--,--,--)
     */
    public static final String NOTIN = "notin";
    /**
     * 條件匹配符 is null
     */
    public static final String IS = "is";
    /**
     * 條件匹配符 is not null
     */
    public static final String ISNOT = "isnot";
    /**
     * 條件匹配符 order by --
     */
    public static final String ORDERBY = "orderby";
    /**
     * 條件匹配符 group by --
     */
    public static final String GROUPBY = "groupby";

    // 轉化數據類型
    /**
     * 轉化int類型
     */
    public static final String TOINT = "int";
    /**
     * 轉化date類型
     */
    public static final String TODATE = "date";

}

SearchUtils.java

package com.test.springBoot.autoBuildQuery.common;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

import com.google.common.collect.Maps;

/**
 * Automatic assembling the query conditions. (org.springframework.data.jpa.domain.Specification)
 *
 */
public class SearchUtils {

    /**
     * Automatic assembling the query conditions.
     * 
     * @param root
     * @param query
     * @param cb
     * @param model : tableColumnName_SqlOperator_convertDataType
     * @return
     */
    public static CriteriaQuery<?> autoBuildQuery(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder cb, Map<String, Object> model) {
        if (model == null || model.size() == 0) {
            return null;
        }
        // query condition list
        List<Predicate> predicates = new ArrayList<Predicate>();
        List<Order> orderList = new ArrayList<Order>();
        List<Expression<?>> groupList = new ArrayList<Expression<?>>();
        // assemble the query conditions
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            String key = entry.getKey();
            String[] keys = key.split("_");
            Object value = entry.getValue();
            // value is null
            boolean valueIsNull = null == value || (value instanceof String && StringUtils.isBlank((String) value))
                    || (value instanceof Collection && CollectionUtils.isEmpty((Collection<?>) value));
            // field_oper_type : tableColumnName_SqlOperator_convertDataType
            String field = keys[0];
            String oper = keys.length > 1 ? keys[1] : "";
            String type = keys.length > 2 ? keys[2] : "";
            // To validate the field to prevent SQL injection.
            if (validateFieldKey(root.getJavaType(), field) == false) {
                // TODO : Increase the log output information.
                // throw new DataValidateException("查詢字段[" + field + "]不存在");
                continue;
            }
            // reference entity
            Path<String> path = root.get(field);
            if (keys.length == 1) {
                if (valueIsNull == false) {
                    predicates.add(cb.equal(path, convertQueryParamsType(type, value)));
                }
                continue;
            }
            if (keys.length > 1) {
                if (valueIsNull == false) {
                    if (SearchType.EQ.equals(oper)) {
                        predicates.add(cb.equal(path, convertQueryParamsType(type, value)));
                        continue;
                    }
                    if (SearchType.NE.equals(oper)) {
                        predicates.add(cb.notEqual(path, convertQueryParamsType(type, value)));
                        continue;
                    }
                    if (SearchType.LT.equals(oper)) {
                        predicates.add(convertParamsTypeAndBuildQuery(oper, cb, path, type, value));
                        continue;
                    }
                    if (SearchType.LE.equals(oper)) {
                        predicates.add(convertParamsTypeAndBuildQuery(oper, cb, path, type, value));
                        continue;
                    }
                    if (SearchType.GT.equals(oper)) {
                        predicates.add(convertParamsTypeAndBuildQuery(oper, cb, path, type, value));
                        continue;
                    }
                    if (SearchType.GE.equals(oper)) {
                        predicates.add(convertParamsTypeAndBuildQuery(oper, cb, path, type, value));
                        continue;
                    }
                    if (SearchType.LIKE.equals(oper)) {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append("%" + escapeSQLLike(convertQueryParamsType(type, value)) + "%");
                        predicates.add(cb.like(path, stringBuilder.toString()));
                        continue;
                    }
                    if (SearchType.NOTLIKE.equals(oper)) {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append("%" + escapeSQLLike(convertQueryParamsType(type, value)) + "%");
                        predicates.add(cb.notLike(path, stringBuilder.toString()));
                        continue;
                    }
                    if (SearchType.START.equals(oper)) {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append(escapeSQLLike(convertQueryParamsType(type, value)) + "%");
                        predicates.add(cb.like(path, stringBuilder.toString()));
                        continue;
                    }
                    if (SearchType.NOTSTART.equals(oper)) {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append(escapeSQLLike(convertQueryParamsType(type, value)) + "%");
                        predicates.add(cb.notLike(path, stringBuilder.toString()));
                        continue;
                    }
                    if (SearchType.IN.equals(oper)) {
                        predicates.add(path.in(convertQueryParamsType(type, value)));
                        continue;
                    }
                    if (SearchType.NOTIN.equals(oper)) {
                        predicates.add(path.in(convertQueryParamsType(type, value)).not());
                        continue;
                    }
                    // ORDERBY's value may not be null
                    if (SearchType.ORDERBY.equals(oper)) {
                        if (StringUtils.equals(value.toString(), "desc")) {
                            orderList.add(cb.desc(path));
                        } else {
                            // if (StringUtils.equals(value.toString(), "asc")) {
                            orderList.add(cb.asc(path));
                        }
                        continue;
                    }
                } else {
                    if (SearchType.IS.equals(oper)) {
                        predicates.add(cb.isNull(path));
                        continue;
                    }
                    if (SearchType.ISNOT.equals(oper)) {
                        predicates.add(cb.isNotNull(path));
                        continue;
                    }
                    // ORDERBY's value may be null
                    if (SearchType.ORDERBY.equals(oper)) {
                        orderList.add(cb.asc(path));
                        continue;
                    }
                    // GROUPBY's value must be null.
                    if (SearchType.GROUPBY.equals(oper)) {
                        groupList.add(path);
                        continue;
                    }
                }
            }
        }
        query.where(predicates.toArray(new Predicate[predicates.size()]));
        query.orderBy(orderList);
        query.groupBy(groupList);
        return query;
    }

    /**
     * Conversion BuildQuery data type of the parameter.
     * @param type
     * @param value
     * @return
     */
    private static Object convertQueryParamsType(String type, Object value) {
        if (StringUtils.isBlank(type)) {
            return value.toString();
        }
        if (SearchType.TOINT.equals(type)) {
            if (value instanceof Collection) {
                if (CollectionUtils.isNotEmpty((Collection<?>) value)) {
                    value = CollectionUtils.collect((Collection<?>) value, new Transformer() {
                        @Override
                        public Integer transform(Object input) {
                            return Integer.parseInt((String) input);
                        }
                    });
                    return value;
                }
            } else if (!(value instanceof Integer)) {
                return Integer.parseInt((String) value);
            }
        } else if (SearchType.TODATE.equals(type)) {
            if (value instanceof Collection) {
                if (CollectionUtils.isNotEmpty((Collection<?>) value)) {
                    value = CollectionUtils.collect((Collection<?>) value, new Transformer() {

                        @Override
                        public Date transform(Object input) {
                            try {
                                return DateUtils.parseDate(input.toString());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            return null;
                        }

                    });
                    return value;
                }
            } else if (!(value instanceof Date)) {
                try {
                    return DateUtils.parseDate(value.toString());
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }
        return value.toString();
    }

    /**
     * Conversion BuildQuery data type of the parameter, and assemble the query.
     * @param oper
     * @param cb
     * @param path
     * @param type
     * @param value
     * @return Predicate
     */
    private static Predicate convertParamsTypeAndBuildQuery(String oper, CriteriaBuilder cb, Path<String> path, String type, Object value) {
        // field_oper_type : type is null, don't need to transform
        if (StringUtils.isBlank(type)) {
            String valueStr = value.toString();
            if (SearchType.LT.equals(oper)) {
                return cb.lessThan(path, valueStr);
            }
            if (SearchType.LE.equals(oper)) {
                return cb.lessThanOrEqualTo(path, valueStr);
            }
            if (SearchType.GT.equals(oper)) {
                return cb.greaterThan(path, valueStr);
            }
            if (SearchType.GE.equals(oper)) {
                return cb.greaterThanOrEqualTo(path, valueStr);
            }
        }
        if (SearchType.TOINT.equals(type)) {
            boolean isTOINT = false;
            if (value instanceof Collection) {
                if (CollectionUtils.isNotEmpty((Collection<?>) value)) {
                    value = CollectionUtils.collect((Collection<?>) value, new Transformer() {
                        @Override
                        public Integer transform(Object input) {
                            return Integer.parseInt((String) input);
                        }
                    });
                }
                isTOINT = true;
            }
            if (!(value instanceof Integer)) {
                isTOINT = true;
            }
            if (isTOINT) {
                int valueInt = Integer.parseInt((String) value);
                if (SearchType.LT.equals(oper)) {
                    return cb.lessThan(path.as(Integer.class), valueInt);
                }
                if (SearchType.LE.equals(oper)) {
                    return cb.lessThanOrEqualTo(path.as(Integer.class), valueInt);
                }
                if (SearchType.GT.equals(oper)) {
                    return cb.greaterThan(path.as(Integer.class), valueInt);
                }
                if (SearchType.GE.equals(oper)) {
                    return cb.greaterThanOrEqualTo(path.as(Integer.class), valueInt);
                }
            }
        }
        if (SearchType.TODATE.equals(type)) {
            boolean isTODATE = false;
            if (value instanceof Collection) {
                if (CollectionUtils.isNotEmpty((Collection<?>) value)) {
                    value = CollectionUtils.collect((Collection<?>) value, new Transformer() {
                        @Override
                        public Date transform(Object input) {
                            try {
                                return DateUtils.parseDate(input.toString());
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            return null;
                        }
                    });
                    isTODATE = true;
                }
            }
            if (!(value instanceof Date)) {
                isTODATE = true;
            }
            if (isTODATE) {
                Date valueDate = null;
                try {
                    valueDate = DateUtils.parseDate(value.toString());
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                if (SearchType.LT.equals(oper)) {
                    return cb.lessThan(path.as(Date.class), valueDate);
                }
                if (SearchType.LE.equals(oper)) {
                    return cb.lessThanOrEqualTo(path.as(Date.class), valueDate);
                }
                if (SearchType.GT.equals(oper)) {
                    return cb.greaterThan(path.as(Date.class), valueDate);
                }
                if (SearchType.GE.equals(oper)) {
                    return cb.greaterThanOrEqualTo(path.as(Date.class), valueDate);
                }
            }
        }
        return null;
    }

    /**
     * Conversion BuildQuery data type of the parameter, and assemble the query.
     * @param entityClassMap
     * @param fieldName
     * @return
     */
    private static boolean validateFieldKey(Map<String, Class<?>> entityClassMap, String fieldName) {
        String alias = "";
        if (fieldName.contains(".")) {
            alias = fieldName.split("\\.")[0];
            fieldName = fieldName.split("\\.")[1];
        }

        Annotation annotation = null;
        if (entityClassMap.containsKey(alias)) {
            String getterMethodName = "get" + StringUtils.capitalize(fieldName);
            Method method = null;
            try {
                method = entityClassMap.get(alias).getMethod(getterMethodName);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (method != null && method.isAnnotationPresent(Column.class)) {
                annotation = method.getAnnotation(Column.class);
            }
        }

        return annotation != null;
    }

    /**
     * Validation of the BuildQuery field name is legal.
     * @param entityClass
     * @param fieldName
     * @return
     */
    private static boolean validateFieldKey(Class<?> entityClass, String fieldName) {
        Map<String, Class<?>> entityClassMap = Maps.newHashMap();
        entityClassMap.put("", entityClass);
        return validateFieldKey(entityClassMap, fieldName);
    }

    /**
     * avoid SQL injection
     * @param likeStr
     * @return
     */
    private static String escapeSQLLike(Object likeStr) {
        String str = likeStr.toString();
        str = StringUtils.replace(str, "_", "/_");
        str = StringUtils.replace(str, "%", "/%");
        str = StringUtils.replace(str, "/", "//");
        return str;
    }

}

10、Service (PersonService.java)改寫,實現查詢條件自動拼裝;

package com.test.springBoot.autoBuildQuery.service;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import com.test.springBoot.autoBuildQuery.common.SearchUtils;
import com.test.springBoot.autoBuildQuery.dao.PersonRepository;
import com.test.springBoot.autoBuildQuery.entity.Person;

@Service
public class PersonService {

    @Resource
    private PersonRepository personRepository;

    public List<Person> findListPerson(final Map<String, Object> params) {
        Specification<Person> spec = new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                SearchUtils.autoBuildQuery(root, query, cb, params);
                return null;
            }
        };
        List<Person> list = personRepository.findAll(spec);
        return list;
    }

}

11、測試(通過改變Controller的參數,正常是前端傳);
http://localhost:8080/autoBuildQuery

完!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章