背景:
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.0modelVersion>
<groupId>com.test.springBootgroupId>
<artifactId>autoBuildQueryartifactId>
<version>0.0.1-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.2.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.5version>
dependency>
<dependency>
<groupId>org.apache.directory.studiogroupId>
<artifactId>org.apache.commons.collectionsartifactId>
<version>3.2.1version>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>15.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
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, JpaSpecificationExecutor {
}
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 findListPerson(final Map params) {
List list = personRepository.findAll(new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
// build query condition
List predicates = new ArrayList();
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 params = Maps.newHashMap();
// params.put("name_like", "张");
// params.put("age_ge_int", "2");
List 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 model) {
if (model == null || model.size() == 0) {
return null;
}
// query condition list
List predicates = new ArrayList();
List orderList = new ArrayList();
List> groupList = new ArrayList>();
// assemble the query conditions
for (Map.Entry 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 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 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> 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> 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 findListPerson(final Map params) {
Specification spec = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
SearchUtils.autoBuildQuery(root, query, cb, params);
return null;
}
};
List list = personRepository.findAll(spec);
return list;
}
}
11、测试(通过改变Controller的参数,正常是前端传);
http://localhost:8080/autoBuildQuery
完!