JSONSQL:使用SQL过滤JSON类型数据(支持多种数据库常用查询、统计、平均值、最大值、最小值、求和语法)

1. 简介#

  在开发中,经常需要根据条件过滤大批量的JSON类型数据。如果仅需要过滤这一种类型,将JSON转为List后过滤即可;如果相同的条件既想过滤数据库表中的数据、也想过滤内存中JSON数据,甚至想过滤Elasticsearch中的数据,那可使用本博客中的方案,使用标准的SQL语法实现多种数据源中的查询或聚合操作。

2. JSONSQL能力总览#

能力 明细
支持的数据库 Mysql PostgreSQL等(采用Druid连接池中的SQL解析器,理论上支持Druid支持的所有数据库)
支持的语法 查询全部、查询指定列、条件查询(=、!=、>、<、>=、<=、between、in、not in、like、not like、为空、不为空)、复杂条件查询(多个and/or组合)、order by、limit、group by(一维或多维)、count、min、max、avg、sum等,其他聚合函数可自行增加
支持的函数 cast、concat、lower、upper、to_timestamp、from_unixtime、PostgreSQL(json_contain、json_not_contain、jsonb_array_length)、Mysql(json_contains、json_length)等,其他函数可自行增加

3. 相关博客#

  DB SQL 转 ES DSL(支持多种数据库常用查询、统计、平均值、最大值、最小值、求和语法)

4. 示例代码#

4.1 创建项目

4.2 修改pom.xml
Copy
<?xml version="1.0" encoding="UTF-8"?> <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.c3stones</groupId> <artifactId>spring-boot-json-sql-demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.10</version> </parent> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.32</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.15</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.18</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
4.3 创建枚举类
Copy
import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Getter; /** * 聚合类型 枚举类 * * @author CL */ @Getter @AllArgsConstructor public enum AggregationType { COUNT("count", "统计"), MAX("max", "最大值"), MIN("min", "最小值"), AVG("avg", "平均值"), SUM("sum", "求和"), ; private final String value; private final String name; /** * 根据值获取枚举 * * @param value 值 * @return {@link AggregationType} */ public static AggregationType findByValue(String value) { if (StrUtil.isNotEmpty(value)) { for (AggregationType aggregationType : values()) { if (StrUtil.equalsIgnoreCase(aggregationType.getValue(), value)) { return aggregationType; } } } return null; } }
Copy
import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.stream.Stream; /** * 操作符 枚举类 * * @author CL */ @Getter @AllArgsConstructor public enum Operator { EQ(" = ", "=="), NEQ(" != ", "!="), GT(" > ", ">"), LT(" < ", "<"), GTE(" >= ", ">="), LTE(" <= ", "<="), IN(" in ", "in", "json_contain", "json_contains"), NOT_IN(" not in ", "notIn", "json_not_contain"), CONTAIN(" like ", "contain"), NOT_CONSTAIN(" not like ", "notContain"), EMPTY(" is null ", "isNull"), NOT_EMPTY(" is not null ", "notNull"), START_WITH(CONTAIN.character, "startWith"), END_WITH(CONTAIN.character, "endWith"), RANGE(" between ", "range"), ; private final String character; private final List<String> operators; Operator(String character, String... operators) { this.character = character; this.operators = Arrays.asList(operators); } /** * 根据值获取枚举 * * @param value 值 * @return {@link Operator} */ public static Operator findByValue(String value) { if (StrUtil.isNotEmpty(value)) { for (Operator operator : values()) { if (StrUtil.equalsAnyIgnoreCase(value, operator.getOperators().toArray(new String[0]))) { return operator; } } } return EQ; } /** * 判断字符串 * * @param source 源 * @param operator 操作符 * @param target 目标 * @return {@link Boolean} */ public static boolean judgeStr(String source, Operator operator, String target) { boolean result = false; if (!(StrUtil.isEmpty(source) || StrUtil.isEmpty(target))) { switch (operator) { case EQ: result = StrUtil.equals(source, target); break; case NEQ: result = !StrUtil.equals(source, target); break; case CONTAIN: result = StrUtil.contains(source, target); break; case NOT_CONSTAIN: result = !StrUtil.contains(source, target); break; case IN: result = StrUtil.equalsAny(source, target.split(StrUtil.COMMA)); break; case NOT_IN: result = !StrUtil.equalsAny(source, target.split(StrUtil.COMMA)); break; case START_WITH: result = StrUtil.startWith(source, target); break; case END_WITH: result = StrUtil.endWith(source, target); break; default: } } return result; } /** * 判断数字 * * @param source 源 * @param operator 操作符 * @param targets 值 * @return {@link Boolean} */ public static boolean judgeNumber(Number source, Operator operator, Number... targets) { boolean result = false; if (!(Objects.isNull(source) || Objects.isNull(targets))) { double src = NumberUtil.toDouble(source); Double[] values = Stream.of(targets).map(NumberUtil::toDouble).toArray(Double[]::new); switch (operator) { case EQ: result = NumberUtil.equals(src, values[0]); break; case NEQ: result = !NumberUtil.equals(src, values[0]); break; case GT: result = src > Opt.ofNullable(values[0]).orElse(Double.MAX_VALUE); break; case LT: result = src < Opt.ofNullable(values[0]).orElse(Double.MIN_VALUE); break; case GTE: result = src >= Opt.ofNullable(values[0]).orElse(Double.MAX_VALUE); break; case LTE: result = src <= Opt.ofNullable(values[0]).orElse(Double.MIN_VALUE); break; case IN: result = StrUtil.equalsAny(source.toString(), Stream.of(targets) .map(Number::toString).toArray(String[]::new)); break; case NOT_IN: result = !StrUtil.equalsAny(source.toString(), Stream.of(targets) .map(Number::toString).toArray(String[]::new)); break; case RANGE: result = src >= Opt.ofNullable(values[0]).orElse(Double.MAX_VALUE) && src <= Opt.ofNullable(values[1]).orElse(Double.MIN_VALUE); break; default: } } return result; } /** * 判断时间 * * @param before 之前时间 * @param operator 操作符 * @param after 之后时间 * @return {@link Boolean} */ public static boolean judgeDate(Date before, Operator operator, Date after) { if (Objects.isNull(before) || Objects.isNull(after)) { return false; } return judgeNumber(before.getTime(), operator, after.getTime()); } /** * 判断数组 * * @param sources 源 * @param operator 操作符 * @param targets 值 * @return {@link Boolean} */ public static boolean judgeArray(Object[] sources, Operator operator, Object... targets) { boolean result = false; if (!(Objects.isNull(sources) || Objects.isNull(targets))) { String[] src = Stream.of(sources).filter(Objects::nonNull).map(Objects::toString).toArray(String[]::new); String[] values = Stream.of(targets).filter(Objects::nonNull).map(Objects::toString).toArray(String[]::new); switch (operator) { case IN: result = ArrayUtil.containsAny(src, values); break; case NOT_IN: result = !ArrayUtil.containsAny(src, values); break; default: } } return result; } /** * 反转操作符 * * @param operator 操作符 * @return {@link Operator} */ public static Operator reverse(Operator operator) { if (Objects.nonNull(operator)) { switch (operator) { case EQ: operator = NEQ; break; case NEQ: operator = EQ; break; case GT: operator = LT; break; case LT: operator = GT; break; case GTE: operator = LTE; break; case LTE: operator = GTE; break; case IN: operator = NOT_IN; break; case NOT_IN: operator = IN; break; case CONTAIN: operator = NOT_CONSTAIN; break; case NOT_CONSTAIN: operator = CONTAIN; break; case EMPTY: operator = NOT_EMPTY; break; case NOT_EMPTY: operator = EMPTY; break; case START_WITH: operator = END_WITH; break; case END_WITH: operator = START_WITH; break; default: } } return operator; } }
Copy
import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Getter; /** * 排序类型 枚举类 * * @author CL */ @Getter @AllArgsConstructor public enum OrderType { ASC, DESC, ; /** * 根据值获取枚举 * * @param value 值 * @return {@link OrderType} */ public static OrderType findByValue(String value) { if (StrUtil.isNotEmpty(value)) { for (OrderType orderType : values()) { if (StrUtil.equalsIgnoreCase(orderType.toString(), value)) { return orderType; } } } return ASC; } }
Copy
import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Objects; /** * 条件关系 枚举类 * * @author CL */ @Getter @AllArgsConstructor public enum Relation { AND(" and "), OR(" or "), NOT(" not "), ; private final String value; /** * 根据值获取枚举 * * @param value 值 * @return {@link Relation} */ public static Relation findByValue(String value) { if (StrUtil.isNotEmpty(value)) { for (Relation relation : values()) { if (StrUtil.equalsIgnoreCase(relation.getValue().trim(), value)) { return relation; } } } return AND; } /** * 反转关系 * * @param relation 关系 * @return {@link Relation} */ public static Relation reverse(Relation relation) { if (Objects.equals(AND, relation)) { return OR; } else if (Objects.equals(OR, relation)) { return AND; } return relation; } }
4.4 创建模型类(JSONSQL结构化实体)
Copy
import com.alibaba.fastjson2.JSONObject; import lombok.Data; import lombok.NoArgsConstructor; /** * JSON 数据模型 * * @author CL */ @Data @NoArgsConstructor public class DataModel { /** * 数据 */ private JSONObject data; public DataModel(JSONObject data) { this.data = data; } @Override public String toString() { return JSONObject.toJSONString(this); } }
Copy
import com.alibaba.fastjson2.JSONObject; import com.c3stones.json.enums.Operator; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * JSONSQL 二元运算表达式 * * @author CL */ @Data @NoArgsConstructor @Accessors(chain = true) public class JQLBinaryOpExpr<T> { /** * 字段名称 */ private String fieldName; /** * 操作符 */ private Operator operator; /** * 值 */ private T[] values; @SafeVarargs public final JQLBinaryOpExpr setValues(T... values) { this.values = values; return this; } @Override public String toString() { return JSONObject.toJSONString(this); } }
Copy
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import com.c3stones.json.enums.Relation; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.util.List; /** * JSONSQL 查询模型 * * @author CL */ @Data @NoArgsConstructor @Accessors(chain = true) public class JQLWhereModel { /** * 关系 */ private Relation relation; /** * 二位运算表达式 */ private List<JQLBinaryOpExpr> binaryOpExprList; /** * 子查询模型 */ private List<JQLWhereModel> childWhereList; @Override public String toString() { return JSONObject.toJSONString(this, JSONWriter.Feature.ReferenceDetection); } }
Copy
import com.alibaba.fastjson2.JSONObject; import com.c3stones.json.enums.AggregationType; import lombok.Data; import lombok.NoArgsConstructor; /** * JSON 聚合模型 * * @author CL */ @Data @NoArgsConstructor public class JQLAggregationModel { /** * 聚合类型 */ private AggregationType aggregationType; /** * 属性名称 */ private String fieldName; /** * 别名 */ private String alias; public JQLAggregationModel(AggregationType aggregationType, String fieldName, String alias) { this.aggregationType = aggregationType; this.fieldName = fieldName; this.alias = alias; } @Override public String toString() { return JSONObject.toJSONString(this); } }
Copy
import com.alibaba.fastjson2.JSONObject; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * JSONSQL 分组模型 * * @author CL */ @Data @NoArgsConstructor @AllArgsConstructor public class JQLGroupModel { /** * 分组属性名称 */ private List<String> groupNameList; @Override public String toString() { return JSONObject.toJSONString(this); } }
Copy
import com.alibaba.fastjson2.JSONObject; import com.c3stones.json.enums.OrderType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * JSON 排序模型 * * @author CL */ @Data @NoArgsConstructor @AllArgsConstructor public class JQLSortModel { /** * 属性名称 */ private String fieldName; /** * 排序类型 */ private OrderType orderType; @Override public String toString() { return JSONObject.toJSONString(this); } }
Copy
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * JSONSQL 结构模型 * * @author CL */ @Data @NoArgsConstructor public class JQLModel { /** * 条件 */ private JQLWhereModel where; /** * 聚合 */ private List<JQLAggregationModel> aggregations; /** * 分组 */ private JQLGroupModel group; /** * 排序 */ private List<JQLSortModel> sorts; /** * 起始位置 */ private Integer from; /** * 大小 */ private Integer size; @Override public String toString() { return JSONObject.toJSONString(this, JSONWriter.Feature.ReferenceDetection); } }
Copy
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSONObject; import com.c3stones.json.mapper.model.DataModel; import com.c3stones.json.mapper.model.JQLModel; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.util.List; import java.util.StringJoiner; import java.util.stream.Collectors; /** * JSON Mapper Provider * * @author CL */ @Data @NoArgsConstructor @Accessors(chain = true) public class JSONMapperProvider { /** * 数据来源 */ @Deprecated private String json; /** * 数据模型 */ private List<DataModel> dataList; /** * JSONSQL */ private JQLModel jql; /** * 包含列 */ private String[] includes; /** * 排除列 */ private String[] excludes; /** * SQL */ private String sql; public JSONMapperProvider(String sql) { this.sql = sql; } @Override public String toString() { StringJoiner str = new StringJoiner(StrUtil.LF); str.add("data : " + Opt.ofNullable(dataList) .orElse(ListUtil.empty()).stream().map(DataModel::toString) .collect(Collectors.joining(StrUtil.LF))); str.add("jql : " + jql); if (ArrayUtil.isNotEmpty(includes)) { str.add("includes : " + JSONObject.toJSONString(includes)); } if (ArrayUtil.isNotEmpty(excludes)) { str.add("excludes : " + JSONObject.toJSONString(excludes)); } str.add("sql : " + sql); return str.toString(); } }
4.5 创建JSONSQL默认查询语法接口
Copy
import cn.hutool.core.collection.CollUtil; import com.c3stones.json.enums.Relation; import com.c3stones.json.mapper.model.*; import java.util.List; import java.util.Objects; /** * JSONSQL 默认查询语法接口 * * @author CL */ @SuppressWarnings("all") public interface JSONDefaultSelect { /** * 等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr eq(String fieldName, Object value); /** * 不等 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr neq(String fieldName, Object value); /** * 大于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr gt(String fieldName, Object value); /** * 大于等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr gte(String fieldName, Object value); /** * 小于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr lt(String fieldName, Object value); /** * 小于等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr lte(String fieldName, Object value); /** * 属于 * * @param fieldName 属性名称 * @param values 值(不能包含英文逗号) * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr in(String fieldName, List<Object> values); /** * 不属于 * * @param fieldName 属性名称 * @param values 值(不能包含英文逗号) * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr notIn(String fieldName, List<Object> values); /** * 包含 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr contain(String fieldName, Object value); /** * 不包含 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr notContain(String fieldName, Object value); /** * 为空 * * @param fieldName 属性名称 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr empty(String fieldName); /** * 不为空 * * @param fieldName 属性名称 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr notEmpty(String fieldName); /** * 在两者之间 * * @param fieldName 属性名称 * @param startValue 起始值 * @param endValue 终止值 * @return {@link JQLBinaryOpExpr} */ JQLBinaryOpExpr range(String fieldName, Object startValue, Object endValue); /** * 关系与 * * @param binaryOpExprs 二元运算表达式 * @return {@link JQLWhereModel} */ default JQLWhereModel and(JQLBinaryOpExpr... binaryOpExprs) { return new JQLWhereModel().setRelation(Relation.AND).setBinaryOpExprList(CollUtil.newArrayList(binaryOpExprs)); } /** * 关系 * * @param binaryOpExprs 二元运算表达式 * @return {@link JQLWhereModel} */ default JQLWhereModel or(JQLBinaryOpExpr... binaryOpExprs) { return new JQLWhereModel().setRelation(Relation.OR).setBinaryOpExprList(CollUtil.newArrayList(binaryOpExprs)); } /** * 子查询模型 * * @param relation 子模型之间的关系 * @param childWheres 子查询模型 * @return {@link JQLWhereModel} */ default JQLWhereModel child(Relation relation, JQLWhereModel... childWheres) { return new JQLWhereModel().setRelation(relation).setChildWhereList(CollUtil.newArrayList(childWheres)); } /** * 查询全部 * * @return {@link JQLWhereModel} */ default JQLWhereModel all() { return new JQLWhereModel(); } /** * JQL * * @param where 条件 * @param aggregations 聚合 * @param group 分组 * @param sorts 排序 * @param from 偏移数 * @param size 限制数 * @return {@link JQLModel} */ default JQLModel jql(JQLWhereModel where, List<JQLAggregationModel> aggregations, JQLGroupModel group, List<JQLSortModel> sorts, Integer from, Integer size) { JQLModel jql = new JQLModel(); jql.setWhere(where); if (CollUtil.isNotEmpty(aggregations)) { jql.setAggregations(aggregations); } if (Objects.nonNull(group)) { jql.setGroup(group); } if (CollUtil.isNotEmpty(sorts)) { jql.setSorts(sorts); } if (Objects.nonNull(from)) { jql.setFrom(from); } if (Objects.nonNull(size)) { jql.setSize(size); } return jql; } }
4.6 创建JSONSQL字符串查询语法
Copy
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.StrUtil; import com.c3stones.json.enums.Operator; import com.c3stones.json.mapper.model.JQLBinaryOpExpr; import com.c3stones.json.parser.select.JSONDefaultSelect; import java.util.List; import java.util.Objects; /** * JSONSQL 字符串查询语法 * * @author CL */ @SuppressWarnings("unchecked") public class StringSelect implements JSONDefaultSelect { /** * 格式化值 * * @param value 值 * @return {@link String} */ private String format(Object value) { return StrUtil.toStringOrNull(value); } /** * 格式化值 * * @param value 值 * @return {@link String[]} */ private String[] format(List<Object> value) { return Opt.ofNullable(value).orElse(ListUtil.empty()).stream().map(StrUtil::toStringOrNull) .filter(Objects::nonNull).toArray(String[]::new); } /** * 格式化 * * @param value 值 * @return {@link String} */ private String format(String value) { return StrUtil.removeSuffix(StrUtil.removePrefix(value, "%"), "%"); } /** * 等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> eq(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.EQ).setValues(format(value)); } /** * 不等 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> neq(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.NEQ).setValues(format(value)); } /** * 大于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> gt(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.GT).setValues(format(value)); } /** * 大于等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> gte(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.GTE).setValues(format(value)); } /** * 小于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> lt(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.LT).setValues(format(value)); } /** * 小于等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> lte(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.LTE).setValues(format(value)); } /** * 属于 * * @param fieldName 属性名称 * @param values 值(不能包含英文逗号) * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> in(String fieldName, List<Object> values) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.IN).setValues(format(values)); } /** * 不属于 * * @param fieldName 属性名称 * @param values 值(不能包含英文逗号) * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> notIn(String fieldName, List<Object> values) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.NOT_IN).setValues(format(values)); } /** * 包含 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> contain(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.CONTAIN).setValues(format(format(value))); } /** * 不包含 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> notContain(String fieldName, Object value) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.NOT_CONSTAIN).setValues(format(format(value))); } /** * 为空 * * @param fieldName 属性名称 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> empty(String fieldName) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.EMPTY); } /** * 不为空 * * @param fieldName 属性名称 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> notEmpty(String fieldName) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.NOT_EMPTY); } /** * 在两者之间 * * @param fieldName 属性名称 * @param startValue 起始值 * @param endValue 终止值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<String> range(String fieldName, Object startValue, Object endValue) { return new JQLBinaryOpExpr<String>().setFieldName(fieldName).setOperator(Operator.RANGE).setValues(format(startValue), format(endValue)); } }
4.7 创建JSONSQL数字查询语法
Copy
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import com.c3stones.json.enums.Operator; import com.c3stones.json.mapper.model.JQLBinaryOpExpr; import com.c3stones.json.parser.select.JSONDefaultSelect; import java.util.List; import java.util.Objects; /** * JSONSQL 数字查询语法 * * @author CL */ @SuppressWarnings("unchecked") public class NumberSelect implements JSONDefaultSelect { /** * 格式化值 * * @param value 值 * @return {@link String} */ private Number format(Object value) { return NumberUtil.parseNumber(StrUtil.toStringOrNull(value)); } /** * 格式化值 * * @param value 值 * @return {@link Number[]} */ private Number[] format(List<Object> value) { return Opt.ofNullable(value).orElse(ListUtil.empty()).stream().map(StrUtil::toStringOrNull) .filter(Objects::nonNull).map(NumberUtil::parseNumber).toArray(Number[]::new); } /** * 等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> eq(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.EQ).setValues(format(value)); } /** * 不等 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> neq(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.NEQ).setValues(format(value)); } /** * 大于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> gt(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.GT).setValues(format(value)); } /** * 大于等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> gte(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.GTE).setValues(format(value)); } /** * 小于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> lt(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.LT).setValues(format(value)); } /** * 小于等于 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> lte(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.LTE).setValues(format(value)); } /** * 属于 * * @param fieldName 属性名称 * @param values 值(不能包含英文逗号) * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> in(String fieldName, List<Object> values) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.IN).setValues(format(values)); } /** * 不属于 * * @param fieldName 属性名称 * @param values 值(不能包含英文逗号) * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> notIn(String fieldName, List<Object> values) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.NOT_IN).setValues(format(values)); } /** * 包含 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> contain(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.CONTAIN).setValues(format(value)); } /** * 不包含 * * @param fieldName 属性名称 * @param value 值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> notContain(String fieldName, Object value) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.NOT_CONSTAIN).setValues(format(value)); } /** * 为空 * * @param fieldName 属性名称 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> empty(String fieldName) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.EMPTY); } /** * 不为空 * * @param fieldName 属性名称 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> notEmpty(String fieldName) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.NOT_EMPTY); } /** * 在两者之间 * * @param fieldName 属性名称 * @param startValue 起始值 * @param endValue 终止值 * @return {@link JQLBinaryOpExpr} */ @Override public JQLBinaryOpExpr<Number> range(String fieldName, Object startValue, Object endValue) { return new JQLBinaryOpExpr<Number>().setFieldName(fieldName).setOperator(Operator.RANGE).setValues(format(startValue), format(endValue)); } }
4.8 创建JSONSQL查询语法处理工厂
Copy
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Opt; import com.c3stones.json.parser.select.impl.StringSelect; import com.c3stones.json.parser.select.impl.NumberSelect; import java.util.List; import java.util.Objects; /** * JSONSQL 查询语法处理工厂 * * @author CL */ public class JSONSelectFactory { /** * 根据数据自动匹配查询语法实现类 * * @param obj 数据 * @return {@link JSONDefaultSelect} */ public static JSONDefaultSelect match(Object obj) { if (Objects.nonNull(obj) && Number.class.isAssignableFrom(obj.getClass())) { return match(Number.class); } return any(); } /** * 根据数据自动匹配查询语法实现类 * * @param list 数据 * @return {@link JSONDefaultSelect} */ public static JSONDefaultSelect match(List<Object> list) { if (Opt.ofNullable(list).orElse(ListUtil.empty()).stream().allMatch(value -> Number.class.isAssignableFrom(value.getClass()))) { return numberSelect; } return stringSelect; } /** * 根据数据类型自动匹配查询语法实现类 * * @param clazz 数据类型 * @return {@link JSONDefaultSelect} */ public static JSONDefaultSelect match(Class<?> clazz) { if (Objects.nonNull(clazz) && Number.class.isAssignableFrom(clazz)) { return numberSelect; } return stringSelect; } /** * 随机获取查询语法实现类 * * @return {@link JSONDefaultSelect} */ public static JSONDefaultSelect any() { return stringSelect; } /** * JSONSQL 数字查询语法 */ private static final JSONDefaultSelect numberSelect = new NumberSelect(); /** * JSONSQL 字符串查询语法 */ private static final JSONDefaultSelect stringSelect = new StringSelect(); }
4.9 创建SQL 转 JSONSQL转换类
Copy
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.ast.*; import com.alibaba.druid.sql.ast.expr.*; import com.alibaba.druid.sql.ast.statement.*; import com.alibaba.druid.sql.parser.SQLStatementParser; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.c3stones.json.enums.AggregationType; import com.c3stones.json.enums.Operator; import com.c3stones.json.enums.OrderType; import com.c3stones.json.enums.Relation; import com.c3stones.json.mapper.JSONMapperProvider; import com.c3stones.json.mapper.model.*; import com.c3stones.json.parser.select.JSONDefaultSelect; import com.c3stones.json.parser.select.JSONSelectFactory; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import static cn.hutool.core.text.CharPool.DOUBLE_QUOTES; import static cn.hutool.core.text.StrPool.*; import static com.alibaba.druid.sql.ast.expr.SQLBinaryOperator.*; /** * SQL 转 JSONSQL * * @author CL */ @Slf4j @RequiredArgsConstructor @SuppressWarnings("all") public class JQLConvert { /** * SQL 常量 - * */ public static final String SQL_ASTERISK = "*"; private final JSONDefaultSelect jsonSelectSyntax; /** * 转换 * * @param sql SQL * @return {@link JSONMapperProvider} */ /** * 转换 * * @param sql SQL * @param dbType 数据库类型 * @return {@link JSONMapperProvider} */ public JSONMapperProvider convert(String sql, DbType dbType) { SQLStatementParser sqlStatementParser = new SQLStatementParser(sql, dbType); Opt<SQLSelectQueryBlock> optional = Opt.ofNullable(sqlStatementParser) .map(parser -> (SQLSelectStatement) sqlStatementParser.parseStatement()) .map(SQLSelectStatement::getSelect) .map(sqlSelect -> (SQLSelectQueryBlock) sqlSelect.getQuery()); return optional.isPresent() ? handle(optional.get()).setSql(sql) : new JSONMapperProvider(sql); } /** * 处理SQL * * @param sqlSelectQuery SQL Select查询 * @return {@link JSONMapperProvider} */ private JSONMapperProvider handle(SQLSelectQueryBlock sqlSelectQuery) { // 处理 Select List<SelectField> selectFieldList = handleSelect(sqlSelectQuery.getSelectList()); // 处理 From String json = handleFrom(sqlSelectQuery.getFrom()); // 处理 Where JQLWhereModel where = handleWhere(sqlSelectQuery.getWhere()); // 处理 Aggregation List<JQLAggregationModel> aggregationList = handleAggregate(selectFieldList); // 处理 Group JQLGroupModel group = handleGroup(sqlSelectQuery.getGroupBy()); // 处理 OrderBy List<JQLSortModel> sortList = handleOrderBy(sqlSelectQuery.getOrderBy()); // 处理 Limit Page page = handleLimit(sqlSelectQuery.getLimit()); // 生成JQL Integer from = Opt.ofNullable(page).map(Page::getFrom).get(); Integer size = Opt.ofNullable(page).map(Page::getSize).get(); JQLModel jql = jsonSelectSyntax.jql(where, aggregationList, group, sortList, from, size); String[] includes = selectFieldList.stream().map(field -> Opt.ofNullable(field.getAlias()).orElse(field.getName())) .filter(fieldName -> !StrUtil.equals(SQL_ASTERISK, fieldName) && Objects.nonNull(fieldName)).toArray(String[]::new); return new JSONMapperProvider().setJson(json).setJql(jql).setIncludes(includes); } /** * 处理查询字段 * * @param sqlSelectItemList 查询元素 * @return {@link List<SelectField>} */ private List<SelectField> handleSelect(List<SQLSelectItem> sqlSelectItemList) { return Opt.ofNullable(sqlSelectItemList).orElse(Collections.emptyList()) .stream().map(sqlSelectItem -> { String name = null, alias, methodName = null; alias = sqlSelectItem.getAlias(); // SQL 表达式 SQLExpr sqlExpr = sqlSelectItem.getExpr(); if (sqlExpr instanceof SQLAggregateExpr) { // 聚合查询 SQLAggregateExpr sqlAggregateExpr = (SQLAggregateExpr) sqlExpr; SQLExpr firstSqlExpr = CollUtil.getFirst(sqlAggregateExpr.getArguments()); methodName = sqlAggregateExpr.getMethodName(); if (firstSqlExpr instanceof SQLAllColumnExpr) { name = SQL_ASTERISK; } else if (firstSqlExpr instanceof SQLIdentifierExpr) { name = ((SQLIdentifierExpr) firstSqlExpr).getName(); } alias = Opt.ofNullable(alias).orElse(methodName); } else if (sqlExpr instanceof SQLAllColumnExpr) { // 查询全部 name = SQL_ASTERISK; } else if (sqlExpr instanceof SQLIdentifierExpr) { // 查询指定列 name = ((SQLIdentifierExpr) sqlExpr).getName(); } else if (sqlExpr instanceof SQLPropertyExpr) { // 查询对象列 SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) sqlExpr; name = sqlPropertyExpr.getOwnerName() + StrUtil.DOT + sqlPropertyExpr.getName(); } return new SelectField(name, alias, methodName); }).collect(Collectors.toList()); } /** * 处理 From * * @param sqlTableSource SQL表资源 * @return {@link String} */ private String handleFrom(SQLTableSource sqlTableSource) { String index = null; if (sqlTableSource instanceof SQLExprTableSource) { SQLExpr tableSqlExpr = ((SQLExprTableSource) sqlTableSource).getExpr(); if (tableSqlExpr instanceof SQLIdentifierExpr) { index = ((SQLIdentifierExpr) tableSqlExpr).getName(); } } return index; } /** * 处理 Where条件 * * @param sqlExpr SQL表达式 * @return {@link JQLWhereModel} */ private JQLWhereModel handleWhere(SQLExpr sqlExpr) { if (sqlExpr instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) sqlExpr; SQLBinaryOperator operator = sqlBinaryOpExpr.getOperator(); if (BooleanAnd == operator || BooleanOr == operator) { return handleWhereAndOrOr(sqlBinaryOpExpr, operator); } return handleWhereBinaryOp(sqlBinaryOpExpr); } else if (sqlExpr instanceof SQLInListExpr) { return handleWhereInOrNotIn((SQLInListExpr) sqlExpr); } else if (sqlExpr instanceof SQLBetweenExpr) { return handleWhereBetween((SQLBetweenExpr) sqlExpr); } else if (sqlExpr instanceof SQLMethodInvokeExpr) { return handleMethodInvoke((SQLMethodInvokeExpr) sqlExpr); } return jsonSelectSyntax.all(); } /** * 处理 AND 或 OR * * @param sqlBinaryOpExpr SQL两位元素操作 * @return {@link JQLWhereModel} */ private JQLWhereModel handleWhereAndOrOr(SQLBinaryOpExpr sqlBinaryOpExpr, SQLBinaryOperator sqlBinaryOperator) { SQLExpr leftExpr = sqlBinaryOpExpr.getLeft(); SQLExpr rightExpr = sqlBinaryOpExpr.getRight(); JQLWhereModel left = handleWhere(leftExpr); JQLWhereModel right = handleWhere(rightExpr); Integer leftSize = Opt.ofNullable(left).map(JQLWhereModel::getBinaryOpExprList).map(List::size).orElse(0); Integer rightSize = Opt.ofNullable(right).map(JQLWhereModel::getBinaryOpExprList).map(List::size).orElse(0); Relation relation = (sqlBinaryOperator == BooleanAnd) ? Relation.AND : Relation.OR; if (leftSize > 1 || rightSize > 1 || CollUtil.isNotEmpty(left.getChildWhereList()) || CollUtil.isNotEmpty(right.getChildWhereList())) { return jsonSelectSyntax.child(relation, left, right); } List<JQLBinaryOpExpr> leftList = Opt.ofNullable(left.getBinaryOpExprList()).orElse(CollUtil.newArrayList()); List<JQLBinaryOpExpr> rightList = Opt.ofNullable(right.getBinaryOpExprList()).orElse(CollUtil.newArrayList()); return Objects.equals(Relation.AND, relation) ? jsonSelectSyntax.and(CollUtil.addAll(leftList, rightList).toArray(new JQLBinaryOpExpr[0])) : jsonSelectSyntax.or(CollUtil.addAll(leftList, rightList).toArray(new JQLBinaryOpExpr[0])); } /** * 处理二位元素操作 * * @param sqlExpr SQL表达式 * @return {@link JQLWhereModel} */ private JQLWhereModel handleWhereBinaryOp(SQLBinaryOpExpr sqlExpr) { List<JQLBinaryOpExpr> list = CollUtil.newArrayList(); SQLExpr leftExpr = sqlExpr.getLeft(); SQLExpr rightExpr = sqlExpr.getRight(); // 特殊处理 1 = 1 / 1 != 1 if (leftExpr instanceof SQLIntegerExpr && rightExpr instanceof SQLIntegerExpr) { if (Objects.equals(getValue(leftExpr), getValue(rightExpr))) { if (sqlExpr.getOperator() == SQLBinaryOperator.Equality) { list.add(jsonSelectSyntax.empty(IdUtil.fastUUID())); } else { list.add(jsonSelectSyntax.notEmpty(IdUtil.fastUUID())); } } } else { String fieldName = null; if (leftExpr instanceof SQLIdentifierExpr) { SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) leftExpr; fieldName = sqlIdentifierExpr.getName(); } else if (leftExpr instanceof SQLPropertyExpr) { SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) leftExpr; fieldName = sqlPropertyExpr.getOwnerName() + StrUtil.DOT + sqlPropertyExpr.getName(); } else if (leftExpr instanceof SQLMethodInvokeExpr) { return handleMethodInvoke((SQLMethodInvokeExpr) leftExpr); } Object value = getValue(rightExpr); JSONDefaultSelect matchSelect = JSONSelectFactory.match(value); switch (sqlExpr.getOperator()) { case Equality: list.add(matchSelect.eq(fieldName, value)); break; case NotEqual: case LessThanOrGreater: list.add(matchSelect.neq(fieldName, value)); break; case GreaterThan: list.add(matchSelect.gt(fieldName, value)); break; case GreaterThanOrEqual: list.add(matchSelect.gte(fieldName, value)); break; case LessThan: list.add(matchSelect.lt(fieldName, value)); break; case LessThanOrEqual: list.add(matchSelect.lte(fieldName, value)); break; case Like: list.add(matchSelect.contain(fieldName, value)); break; case NotLike: list.add(matchSelect.notContain(fieldName, value)); break; case Is: list.add(matchSelect.empty(fieldName)); break; case IsNot: list.add(matchSelect.notEmpty(fieldName)); break; default: // no operate } } return jsonSelectSyntax.and(list.toArray(new JQLBinaryOpExpr[0])); } /** * 处理 in 或 notIn * * @param sqlInListExpr SQL In 表达式 * @return {@link JQLWhereModel} */ private JQLWhereModel handleWhereInOrNotIn(SQLInListExpr sqlInListExpr) { SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) sqlInListExpr.getExpr(); String fieldName = sqlIdentifierExpr.getName(); List<Object> values = sqlInListExpr.getTargetList().stream().map(this::getValue).collect(Collectors.toList()); JSONDefaultSelect matchSelect = JSONSelectFactory.match(values); return jsonSelectSyntax.and(sqlInListExpr.isNot() ? matchSelect.notIn(fieldName, values) : matchSelect.in(fieldName, values)); } /** * 处理 between * * @param sqlBetweenExpr SQL Between 表达式 * @return {@link JQLWhereModel} */ private JQLWhereModel handleWhereBetween(SQLBetweenExpr sqlBetweenExpr) { SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) sqlBetweenExpr.getTestExpr(); String field = sqlIdentifierExpr.getName(); Object startValue = getValue(sqlBetweenExpr.getBeginExpr()); Object endValue = getValue(sqlBetweenExpr.getEndExpr()); JSONDefaultSelect matchSelect = JSONSelectFactory.match(Arrays.asList(startValue, endValue)); return jsonSelectSyntax.and(matchSelect.range(field, startValue, endValue)); } /** * 处理函数调用 * * @param sqlMethodInvokeExpr SQL函数调用表达式 * @return {@link JQLWhereModel} */ private JQLWhereModel handleMethodInvoke(SQLMethodInvokeExpr sqlMethodInvokeExpr) { String methodName = sqlMethodInvokeExpr.getMethodName(); List<SQLExpr> arguments = sqlMethodInvokeExpr.getArguments(); SQLExpr leftExpr = CollUtil.get(arguments, 0); SQLExpr rightExpr = CollUtil.get(arguments, 1); String fieldName = null; if (leftExpr instanceof SQLIdentifierExpr) { SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) leftExpr; fieldName = sqlIdentifierExpr.getName(); } else if (leftExpr instanceof SQLPropertyExpr) { SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) leftExpr; fieldName = sqlPropertyExpr.getOwnerName() + StrUtil.DOT + sqlPropertyExpr.getName(); } if (StrUtil.equalsAnyIgnoreCase(methodName, "json_contain", "json_not_contain", "json_contains")) { List<Object> values = null; if (rightExpr instanceof SQLArrayExpr) { values = ((SQLArrayExpr) rightExpr).getValues().stream().map(this::getValue).collect(Collectors.toList()); } else if (rightExpr instanceof SQLCharExpr) { values = Opt.ofNullable(((SQLCharExpr) rightExpr)).map(SQLCharExpr::getValue).map(str -> StrUtil.strip( StrUtil.toStringOrNull(str), StrUtil.toString(C_BRACKET_START), StrUtil.toString(C_BRACKET_END))) .map(str -> StrUtil.strip(str, COMMA)).map(CollUtil::newArrayList).orElse(CollUtil.newArrayList()) .stream().map(str -> StrUtil.strip(StrUtil.toStringOrNull(str), StrUtil.toString(DOUBLE_QUOTES), StrUtil.toString(DOUBLE_QUOTES))).map(StrUtil::trim).collect(Collectors.toList()); } JSONDefaultSelect matchSelect = JSONSelectFactory.match(values); boolean isContain = Objects.equals(Operator.IN, Operator.findByValue(methodName)); SQLObject parent = sqlMethodInvokeExpr.getParent(); if (parent instanceof SQLBinaryOpExpr) { SQLBinaryOperator operator = ((SQLBinaryOpExpr) parent).getOperator(); Boolean value = BooleanUtil.toBoolean(StrUtil.toStringOrNull(getValue(((SQLBinaryOpExpr) parent).getRight()))); if (BooleanUtil.isTrue(value)) { isContain = Objects.equals(Equality, operator) ? true : false; } else { isContain = Objects.equals(Equality, operator) ? false : true; } } return jsonSelectSyntax.and(isContain ? matchSelect.in(fieldName, values) : matchSelect.notIn(fieldName, values)); } else if (StrUtil.equalsAnyIgnoreCase(methodName, "jsonb_array_length", "json_length")) { SQLObject parent = sqlMethodInvokeExpr.getParent(); if (parent instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) parent; JSONDefaultSelect matchSelect = JSONSelectFactory.any(); return jsonSelectSyntax.and(Objects.equals(Equality, sqlBinaryOpExpr.getOperator()) ? matchSelect.empty(fieldName) : matchSelect.notEmpty(fieldName)); } } return jsonSelectSyntax.all(); } /** * 处理聚合函数 * * @param selectFieldList 查询字段 * @return {@link List<JQLAggregationModel>} */ private List<JQLAggregationModel> handleAggregate(List<SelectField> selectFieldList) { List<JQLAggregationModel> list = CollUtil.newArrayList(); for (SelectField selectField : Opt.ofNullable(selectFieldList).orElse(ListUtil.empty())) { AggregationType aggregationType = AggregationType.findByValue(selectField.getMethodName()); if (Objects.isNull(aggregationType)) continue; list.add(new JQLAggregationModel(aggregationType, selectField.getName(), Opt.ofNullable(selectField.getAlias()).orElse(selectField.getMethodName()))); } return list; } /** * 处理分组表 * * @param sqlSelectGroupByClause SQL GroupBy 从句 * @return {@link JQLGroupModel} */ private JQLGroupModel handleGroup(SQLSelectGroupByClause sqlSelectGroupByClause) { if (Objects.nonNull(sqlSelectGroupByClause)) { List<String> groupNameList = CollUtil.newArrayList(); for (SQLExpr sqlExpr : sqlSelectGroupByClause.getItems()) { if (sqlExpr instanceof SQLIdentifierExpr) { groupNameList.add(((SQLIdentifierExpr) sqlExpr).getName()); } } return new JQLGroupModel(groupNameList); } return null; } /** * 处理 OrderBy * * @param sqlOrderBy SQL OrderBy * @return {@link String} */ private List<JQLSortModel> handleOrderBy(SQLOrderBy sqlOrderBy) { List<JQLSortModel> list = CollUtil.newArrayList(); for (SQLSelectOrderByItem sqlSelectOrderByItem : Opt.ofNullable(sqlOrderBy).map(SQLOrderBy::getItems).orElse(ListUtil.empty())) { SQLIdentifierExpr orderBySqlIdentifierExpr = (SQLIdentifierExpr) sqlSelectOrderByItem.getExpr(); SQLOrderingSpecification sqlOrderingSpecification = sqlSelectOrderByItem.getType(); list.add(new JQLSortModel(orderBySqlIdentifierExpr.getName(), OrderType.findByValue(StrUtil.toStringOrNull(sqlOrderingSpecification)))); } return list; } /** * 处理 Limit * * @param sqlLimit SQL Limit * @return {@link Page} */ private Page handleLimit(SQLLimit sqlLimit) { if (Objects.isNull(sqlLimit)) return null; SQLIntegerExpr sqlLimitOffset = (SQLIntegerExpr) sqlLimit.getOffset(); SQLIntegerExpr sqlLimitRowCount = (SQLIntegerExpr) sqlLimit.getRowCount(); Integer from = Objects.isNull(sqlLimitOffset) ? 0 : sqlLimitOffset.getNumber().intValue(); Integer size = sqlLimitRowCount.getNumber().intValue(); return new Page().setFrom(from).setSize(size); } /** * 获取值 * * @param sqlExpr 表达式 * @return {@link Object} */ private Object getValue(SQLExpr sqlExpr) { Object value = null; if (sqlExpr instanceof SQLIntegerExpr) { value = ((SQLIntegerExpr) sqlExpr).getNumber(); } else if (sqlExpr instanceof SQLCharExpr) { value = ((SQLCharExpr) sqlExpr).getText(); } else if (sqlExpr instanceof SQLNumberExpr) { value = ((SQLNumberExpr) sqlExpr).getNumber(); } else if (sqlExpr instanceof SQLMethodInvokeExpr) { SQLMethodInvokeExpr methodInvokeExpr = (SQLMethodInvokeExpr) sqlExpr; String methodName = methodInvokeExpr.getMethodName(); List<SQLExpr> arguments = methodInvokeExpr.getArguments(); if (StrUtil.containsIgnoreCase("concat", methodName)) { value = arguments.stream().map(this::getValue).map(StrUtil::toStringOrNull) .filter(Objects::nonNull).collect(Collectors.joining()); } else if (StrUtil.equalsAnyIgnoreCase(methodName, "lower", "upper")) { return getValue(CollUtil.getFirst(arguments)); } else if (StrUtil.equalsAnyIgnoreCase(methodName, "to_timestamp", "from_unixtime")) { Object tmp = getValue(CollUtil.getFirst(arguments)); return CollUtil.getFirst(StrUtil.split(tmp.toString(), StrUtil.DOT)); } } else if (sqlExpr instanceof SQLCastExpr) { SQLCastExpr sqlCastExpr = (SQLCastExpr) sqlExpr; return getValue(sqlCastExpr.getExpr()); } else if (sqlExpr instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) sqlExpr; return getValue(sqlBinaryOpExpr.getLeft()) + StrUtil.DOT + getValue(sqlBinaryOpExpr.getRight()); } return value; } /** * 字符串转义 * * @param str 字符串 * @return {@link String} */ private String escape(String str) { if (StringUtils.isBlank(str)) return str; StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isWhitespace(c) || c == '\\' || c == '\"' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == ':' || c == '^' || c == '~' || c == '*' || c == '?' || c == '|' || c == '&' || c == ';' || c == '/' || c == '.' || c == '$') { sb.append('\\').append('\\'); } sb.append(c); } return sb.toString(); } /** * 查询字段 */ @Data @NoArgsConstructor @AllArgsConstructor private static class SelectField { /** * 字段名 */ private String name; /** * 别名 */ private String alias; /** * 方法名 */ private String methodName; } /** * 翻页 */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) private static class Page { /** * 开始位置 */ private Integer from; /** * 页大小 */ private Integer size; } }
4.10 创建JSONSQL异常类
Copy
/** * JSONSQL 异常 * * @author CL */ public class JQLException extends RuntimeException { private final ErrorCode errorCode; public JQLException(ErrorCode errorCode) { super(errorCode.getReason()); this.errorCode = errorCode; } public JQLException(ErrorCode errorCode, String message, Throwable cause) { super(message, cause); this.errorCode = errorCode; } @Override public String getMessage() { return this.errorCode.getReason() + ", 建议:" + this.errorCode.getSuggest(); } }
Copy
import lombok.AllArgsConstructor; import lombok.Getter; /** * 错误码 * * @author CL */ @Getter @AllArgsConstructor public enum ErrorCode { PROVIDER_MISS_ERROR( -1, "参数为空", "请调用#{com.c3stones.json.parser.convert.JQLConvert.convert(String sql)}方法得到#{com.c3stones.json.mapper.JSONMapperProvider}" ), DATA_MISS_ERROR( -2, "数据为空", "请调用#{com.*.*.biz.mapper.json.JSONMapperProvider.setDataModel(List<DataModel> dataList)}设置数据。" ), JQL_MISS_ERROR( -3, "JQL为空", "请检查#{com.*.*.biz.mapper.json.JSONMapperProvider}中的#{jql}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.convert(String sql)}方法设置。" ), JQL_WHERE_RELATION_MISS_ERROR( -4, "JQL Where中Relation为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{where}属性中的#{relation}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleWhere(SQLExpr sqlExpr)}方法设置,可能是使用了除[and、or]之外的连接符。" ), JQL_WHERE_BINARYOPEXPR_FIELDNAME_MISS_ERROR( -5, "JQL Where条件中二位操作表达式的属性名称为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{where}属性中的#{binaryOpExprList}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleWhere(SQLExpr sqlExpr)}方法设置,可能是使用了除[json_contain、json_not_contain、jsonb_array_length]之外的函数包装属性名称。" ), JQL_WHERE_BINARYOPEXPR_OPERATOR_MISS_ERROR( -6, "JQL Where条件中二位操作表达式的操作符为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{where}属性中的#{binaryOpExprList}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleWhere(SQLExpr sqlExpr)}方法设置,可能是使用了除[=、!=、>、>=、<、<=、<>、like、not like、is、is not]之外的操作符,或者使用了代码中未适配的函数调用。" ), JQL_AGGREGATION_TYPE_MISS_ERROR( -7, "JQL 聚合操作中聚合类型为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{binaryOpExprList}属性中的#{aggregationType}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleAggregate(List<SelectField> selectFieldList)}方法设置,可能是使用了除[count、max、min、avg、sum]之外的聚合函数。" ), JQL_AGGREGATION_FIELDNAME_MISS_ERROR( -8, "JQL 聚合操作中属性名称为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{binaryOpExprList}属性中的#{fieldName}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleAggregate(List<SelectField> selectFieldList)}方法设置,可能是没有设置属性。" ), JQL_GROUP_FIELNAME_MISS_ERROR( -9, "JQL 分组属性为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{group}属性中的#{groupNameList}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleGroup(SQLSelectGroupByClause sqlSelectGroupByClause)}方法设置,如果设置了属性别名请使用别名,否则请检查是否和属性名称一致,很小可能是没有设置分组属性或使用了函数进行了包装。" ), JQL_SORT_FIELDNAME_MISS_ERROR( -10, "JQL 排序中属性名称为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{sorts}属性中的#{fieldName}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleOrderBy(SQLOrderBy sqlOrderBy)}方法设置,可能是没有设置参数,如果设置了属性别名请使用别名,否则请检查是否和属性名称一致,很小可能是没有设置排序属性。" ), JQL_FROM_MISS_ERROR( -11, "JQL 偏移数为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{from}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleLimit(SQLLimit sqlLimit)}方法设置,它可能小于0,或者没有设置值。" ), JQL_SIZE_MISS_ERROR( -12, "JQL 限制数为空", "请检查#{com.c3stones.json.mapper.model.JQLModel}中的#{size}属性,它由#{com.c3stones.json.parser.convert.JQLConvert.handleLimit(SQLLimit sqlLimit)}方法设置,它可能小于0,或者没有设置值。" ), FILTER_EXECUTE_ERROR( -13, "过滤二元表达式异常", "请检查SQL中的Where条件,可能在#{com.c3stones.json.parser.convert.JQLConvert.convert(String sql)}方法中处理#{com.c3stones.json.mapper.model.JQLWhereModel}时存在不支持的语法,也可能在将不同的数据类型转换成通用的Number或String类型处理时发生异常。如果您熟悉#{com.c3stones.json.mapper.model.JQLWhereModel}的数据定义,直接检查它将会提高解决的效率。" ), ; /** * 错误码 */ private final Integer code; /** * 原因 */ private final String reason; /** * 建议 */ private final String suggest; }
4.11 创建JSONSQL断言类
Copy
import cn.hutool.core.lang.Opt; import com.c3stones.json.enums.Operator; import com.c3stones.json.mapper.JSONMapperProvider; import com.c3stones.json.mapper.exception.ErrorCode; import com.c3stones.json.mapper.exception.JQLException; import com.c3stones.json.mapper.model.*; import org.springframework.util.Assert; import java.math.BigInteger; import java.util.Collection; import java.util.Objects; import static cn.hutool.core.collection.CollUtil.newArrayList; import static cn.hutool.core.collection.ListUtil.empty; /** * JSONSQL 断言 * * @author CL */ public class JQLAssert { /** * 校验JSONSQL语法和 * * @param provider JSON Mapper Provider */ public static void validated(JSONMapperProvider provider) { validated((Object) provider); validated(provider.getDataList()); validated(provider.getJql()); JQLModel jql = provider.getJql(); validated(jql.getWhere()); validated(jql.getAggregations()); validated(jql.getGroup()); validated(jql.getSorts()); validated(jql.getFrom()); validated(jql.getSize()); } /** * 对象断言 * * @param obj 对象 */ private static void validated(Object obj) { if (obj instanceof JSONMapperProvider) { notNull(obj, ErrorCode.PROVIDER_MISS_ERROR); } else if (obj instanceof JQLModel) { notNull(obj, ErrorCode.JQL_MISS_ERROR); } else if (obj instanceof JQLWhereModel) { JQLWhereModel where = (JQLWhereModel) obj; if (Objects.nonNull(where.getBinaryOpExprList()) || Objects.nonNull(where.getChildWhereList())) notNull(obj, ErrorCode.JQL_WHERE_RELATION_MISS_ERROR); Opt.ofNullable(where.getBinaryOpExprList()).orElse(empty()).forEach(expr -> validated(expr)); Opt.ofNullable(where.getChildWhereList()).orElse(empty()).forEach(childWhere -> validated(childWhere)); } else if (obj instanceof JQLBinaryOpExpr) { JQLBinaryOpExpr binaryOpExpr = (JQLBinaryOpExpr) obj; notNull(binaryOpExpr.getFieldName(), ErrorCode.JQL_WHERE_BINARYOPEXPR_FIELDNAME_MISS_ERROR); notNull(binaryOpExpr.getOperator(), ErrorCode.JQL_WHERE_BINARYOPEXPR_OPERATOR_MISS_ERROR); } else if (obj instanceof JQLAggregationModel) { JQLAggregationModel aggregation = (JQLAggregationModel) obj; notNull(aggregation.getAggregationType(), ErrorCode.JQL_AGGREGATION_TYPE_MISS_ERROR); notNull(aggregation.getFieldName(), ErrorCode.JQL_AGGREGATION_FIELDNAME_MISS_ERROR); } else if (obj instanceof JQLGroupModel) { JQLGroupModel group = (JQLGroupModel) obj; noNullElements(group.getGroupNameList(), ErrorCode.JQL_GROUP_FIELNAME_MISS_ERROR); } else if (obj instanceof JQLSortModel) { JQLSortModel sort = (JQLSortModel) obj; notNull(sort.getFieldName(), ErrorCode.JQL_SORT_FIELDNAME_MISS_ERROR); } else if (obj instanceof Integer) { Integer integer = (Integer) obj; gte(integer, BigInteger.ZERO.intValue(), ErrorCode.JQL_FROM_MISS_ERROR); } } /** * 校验集合参数 * * @param collection 集合 */ private static void validated(Collection collection) { Opt.ofNullable(collection).orElse(newArrayList()).forEach(obj -> validated(obj)); } /** * 对象非空断言 * * @param obj 对象 * @param errorCode 错误码 */ private static void notNull(Object obj, ErrorCode errorCode) { Assert.notNull(obj, () -> { throw new JQLException(errorCode); }); } /** * 集合非空断言 * * @param collection 对象 * @param errorCode 错误码 */ private static void noNullElements(Collection collection, ErrorCode errorCode) { Assert.notEmpty(collection, () -> { throw new JQLException(errorCode); }); } /** * a大于等于b断言 * * @param a 数字a * @param b 数字b * @param errorCode 财错误码 */ private static void gte(Number a, Number b, ErrorCode errorCode) { Assert.isTrue(Operator.judgeNumber(a, Operator.GTE, b), () -> { throw new JQLException(errorCode); }); } }
4.12 创建JSONSQL查询Mapper
Copy
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.util.TypeUtils; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; import com.c3stones.json.enums.Operator; import com.c3stones.json.enums.OrderType; import com.c3stones.json.enums.Relation; import com.c3stones.json.mapper.exception.ErrorCode; import com.c3stones.json.mapper.exception.JQLException; import com.c3stones.json.mapper.model.*; import com.c3stones.json.mapper.valid.JQLAssert; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.Configuration; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * JSON Mapper * * @author CL */ @Slf4j @Component public class JSONMapper { @Resource private MybatisPlusProperties mybatisPlusProperties; /** * 聚合 * * @param provider JSON Mapper Provider * @return {@link List} */ public List<Map<String, Object>> aggregation(JSONMapperProvider provider) { return execute(provider); } /** * 查询 * * @param provider JSON Mapper Provider * @return {@link List} */ public List<Map<String, Object>> query(JSONMapperProvider provider) { return execute(provider); } /** * 执行 * * @param provider JSON Mapper Provider * @return {@link List} */ private List<Map<String, Object>> execute(JSONMapperProvider provider) { // 如果开启Mybatis-Plus SQL打印,则同时打印 JQL if (Opt.ofNullable(mybatisPlusProperties).map(MybatisPlusProperties::getConfiguration).map(Configuration::getLogImpl).isPresent() && log.isInfoEnabled()) { log.info(JSONObject.toJSONString(provider)); } JQLAssert.validated(provider); if (CollUtil.isEmpty(provider.getDataList())) return ListUtil.empty(); List<JSONObject> dataList = provider.getDataList().stream().map(DataModel::getData).collect(Collectors.toList()); if (CollUtil.isEmpty(dataList)) return ListUtil.empty(); String[] includes = provider.getIncludes(); String[] excludes = provider.getExcludes(); JQLModel jql = provider.getJql(); JQLWhereModel where = jql.getWhere(); JQLGroupModel group = jql.getGroup(); List<JQLAggregationModel> aggregations = jql.getAggregations(); List<JQLSortModel> sorts = jql.getSorts(); Integer from = jql.getFrom(); Integer size = jql.getSize(); // 过滤 List<JSONObject> filterList = dataList.stream().filter(data -> filter(data, where)).collect(Collectors.toList()); // 过滤完的临时表存储 List<Map<String, Object>> tempTable = createTempTable(filterList); // 分组聚合 tempTable = groupAndAggregation(tempTable, group, aggregations); // 排序 sort(tempTable, sorts); // 限制 tempTable = limit(tempTable, from, size); // 包含列/排除列 return result(tempTable, includes, excludes); } /** * 过滤 * * @param data 数据 * @param where 条件 * @return {@link Boolean} */ private boolean filter(JSONObject data, JQLWhereModel where) { List<Boolean> resultList = CollUtil.newArrayList(); Opt.ofNullable(where.getBinaryOpExprList()).orElse(ListUtil.empty()).forEach(binaryOpExpr -> { String fieldName = binaryOpExpr.getFieldName(); Object source = getValue(data, fieldName); Operator operator = binaryOpExpr.getOperator(); if (Objects.equals(Operator.EMPTY, operator)) { resultList.add(source instanceof Iterable ? CollUtil.isEmpty((Iterable<?>) source) : Objects.isNull(source)); } else if (Objects.equals(Operator.NOT_EMPTY, operator)) { resultList.add(source instanceof Iterable ? CollUtil.isNotEmpty((Iterable<?>) source) : Objects.nonNull(source)); } else { resultList.add(filter(source, operator, binaryOpExpr.getValues())); } }); Opt.ofNullable(where.getChildWhereList()).orElse(ListUtil.empty()).forEach(childWhere -> resultList.add(filter(data, childWhere))); return Objects.equals(Relation.OR, where.getRelation()) ? resultList.stream().anyMatch(result -> result) : resultList.stream().allMatch(result -> result); } /** * 过滤 * * @param source 源数据 * @param operator 操作符 * @param values 目标数据 * @return {@link Boolean} */ private boolean filter(Object source, Operator operator, Object[] values) { boolean temp = false; try { if (Objects.nonNull(source) && Number.class.isAssignableFrom(source.getClass())) { if (values instanceof String[]) { // 可能是时间类型,尝试将时间转成Long values = Stream.of(values).map(value -> DateUtil.parse(TypeUtils.cast(value, String.class))) .map(DateTime::getTime).toArray(Number[]::new); } temp = Operator.judgeNumber(TypeUtils.cast(source, Number.class), operator, TypeUtils.cast(values, Number[].class)); } else if (Objects.nonNull(source) && Date.class.isAssignableFrom(source.getClass())) { temp = Operator.judgeNumber(TypeUtils.toDate(source).getTime(), operator, Stream.of(values).map(value -> DateUtil.parse(TypeUtils.cast(value, String.class))).map(DateTime::getTime).toArray(Number[]::new)); } else if (Objects.nonNull(source) && source instanceof Collection) { temp = Operator.judgeArray(TypeUtils.cast(source, Object[].class), operator, values); } else { temp = Operator.judgeStr(StrUtil.toStringOrNull(source), operator, Stream.of(values) .map(StrUtil::toStringOrNull).filter(Objects::nonNull).collect(Collectors.joining(StrUtil.COMMA))); } } catch (Exception e) { log.warn(StrUtil.EMPTY, new JQLException(ErrorCode.FILTER_EXECUTE_ERROR, e.getMessage(), e)); } return temp; } /** * 创建临时表 * * @param dataList 过滤后的数据 * @return {@link List} */ private List<Map<String, Object>> createTempTable(List<JSONObject> dataList) { List<Map<String, Object>> tempTable = new ArrayList<>(dataList.size()); dataList.forEach(data -> { Set<Map.Entry<String, Object>> entries = data.entrySet(); Map<String, Object> map = new LinkedHashMap<>(entries.size()); for (Map.Entry<String, Object> entry : entries) { map.put(entry.getKey(), entry.getValue()); } tempTable.add(map); }); return tempTable; } /** * 分组并聚合 * * @param tempTable 临时表 * @param group 分组 * @param aggregations 聚合 * @return {@link List} */ private List<Map<String, Object>> groupAndAggregation(List<Map<String, Object>> tempTable, JQLGroupModel group, List<JQLAggregationModel> aggregations) { List<String> groupNameList = Opt.ofNullable(group).map(JQLGroupModel::getGroupNameList).get(); if (CollUtil.isEmpty(groupNameList)) return aggregation(tempTable, aggregations); List<Map<String, Object>> resultList = new ArrayList<>(tempTable.size()); // 分组 Map<Map<String, Object>, List<Map<String, Object>>> collect = tempTable.stream() .collect(Collectors.groupingBy(data -> groupNameList.stream().map(data::get) .map(StrUtil::toString).collect(Collectors.joining()))).values() .stream().map(maps -> { Map<String, Object> key = new LinkedHashMap<>(groupNameList.size()); Map<String, Object> first = CollUtil.getFirst(maps); groupNameList.forEach(groupName -> key.put(groupName, first.get(groupName))); return MapUtil.entry(key, maps); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); // 聚合 collect.forEach((key, value) -> { Map<String, Object> map = MapBuilder.<String, Object>create(true).putAll(key).build(); aggregation(value, aggregations).forEach(map::putAll); resultList.add(map); }); return resultList; } /** * 聚合 * * @param tempTable 临时表 * @param aggregations 聚合 * @return {@link List} */ private List<Map<String, Object>> aggregation(List<Map<String, Object>> tempTable, List<JQLAggregationModel> aggregations) { if (CollUtil.isEmpty(aggregations)) return tempTable; Map<String, Object> map = MapUtil.newHashMap(aggregations.size(), true); int newScale = 4; aggregations.stream().collect(Collectors.groupingBy(JQLAggregationModel::getFieldName)).forEach((fileName, aggregationList) -> { DoubleSummaryStatistics statistics = tempTable.stream().map(data -> StrUtil.toStringOrNull(data.get(fileName))).mapToDouble(data -> Opt.ofTry(() -> NumberUtil.parseDouble(data)).orElse(BigInteger.ZERO.doubleValue())).summaryStatistics(); aggregationList.forEach(aggregation -> { Object value = null; switch (aggregation.getAggregationType()) { case SUM: value = BigDecimal.valueOf(statistics.getSum()).longValue(); break; case MAX: value = BigDecimal.valueOf(statistics.getMax()).setScale(newScale, RoundingMode.HALF_UP).doubleValue(); break; case MIN: value = BigDecimal.valueOf(statistics.getMin()).setScale(newScale, RoundingMode.HALF_UP).doubleValue(); break; case AVG: value = BigDecimal.valueOf(statistics.getAverage()).setScale(newScale, RoundingMode.HALF_UP).doubleValue(); break; case COUNT: value = BigDecimal.valueOf(statistics.getCount()).longValue(); break; } map.put(aggregation.getAlias(), value); }); }); return CollUtil.newArrayList(map); } /** * 排序 * * @param tempTable 临时表 * @param sorts 排序 */ private void sort(List<Map<String, Object>> tempTable, List<JQLSortModel> sorts) { Opt.ofNullable(sorts).orElse(ListUtil.empty()).forEach(sort -> { String fieldName = sort.getFieldName(); OrderType orderType = sort.getOrderType(); tempTable.sort((d1, d2) -> { Integer a = Integer.valueOf(StrUtil.toString((d1.get(fieldName)))); Integer b = Integer.valueOf(StrUtil.toString((d2.get(fieldName)))); return Objects.equals(OrderType.ASC, orderType) ? a - b : b - a; }); }); } /** * 限制 * * @param tempTable 临时表 * @param from 偏移数 * @param size 限制数 * @return {@link List} */ private List<Map<String, Object>> limit(List<Map<String, Object>> tempTable, Integer from, Integer size) { return tempTable.stream().skip(Opt.ofNullable(from).orElse(0)).limit(Opt.ofNullable(size).orElse(tempTable.size())).collect(Collectors.toList()); } /** * 结果 * * @param tempTable 临时表 * @param includes 包含列 * @param excludes 排除列 * @return {@link List} */ private List<Map<String, Object>> result(List<Map<String, Object>> tempTable, String[] includes, String[] excludes) { return tempTable.stream().map(data -> includes(data, includes)).map(data -> excludes(data, includes, excludes)) .collect(Collectors.toList()); } /** * 包含 * * @param data 数据 * @param includes 包含列 * @return {@link Map} */ private Map<String, Object> includes(Map<String, Object> data, String[] includes) { if (ArrayUtil.isEmpty(includes) || MapUtil.isEmpty(data)) return data; Map<String, Object> map = new HashMap<>(data.size()); for (String include : includes) { String json = JSONObject.toJSONString(data); JSONObject jsonObject = JSONObject.parseObject(json); map.put(include, getValue(jsonObject, include)); } return MapUtil.isEmpty(map) ? data : map; } /** * 排除 * * @param data 数据 * @param includes 包含列 * @param excludes 排除列 * @return {@link Map} */ private Map<String, Object> excludes(Map<String, Object> data, String[] includes, String[] excludes) { if (ArrayUtil.isEmpty(excludes) || MapUtil.isEmpty(data)) return data; for (String exclude : excludes) { if (!StrUtil.containsAny(exclude, includes)) { excludes(data, exclude); } else { data.remove(exclude); } } return data; } /** * 排除 * * @param data 数据 * @param exclude 排除列 */ private void excludes(Map<String, Object> data, String exclude) { List<String> split = StrUtil.split(exclude, StrUtil.DOT); int size = CollUtil.size(split); if (size <= 1) { data.remove(exclude); return; } String name = CollUtil.getFirst(split); String subName = StrUtil.join(StrUtil.DOT, CollUtil.sub(split, 1, size)); String json = JSONObject.toJSONString(data.get(name)); if (!JSON.isValidObject(json)) return; JSONObject jsonObject = JSONObject.parseObject(json); excludes(jsonObject, subName); data.put(name, jsonObject); } /** * 获取数据 * * @param data 数据 * @param fieldName 属性名称 * @return {@link Object} */ private Object getValue(JSONObject data, String fieldName) { List<String> split = StrUtil.split(fieldName, StrUtil.DOT); int size = CollUtil.size(split); if (size <= 1) { return data.get(fieldName); } String name = CollUtil.getFirst(split); List<String> subList = CollUtil.sub(split, 1, size); String subName = StrUtil.join(StrUtil.DOT, subList); String json = JSONObject.toJSONString(data.get(name)); if (!JSON.isValidObject(json)) return null; JSONObject jsonObject = JSONObject.parseObject(json); return getValue(jsonObject, subName); } }
4.13 创建启动类
Copy
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 启动类 * * @author CL */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

5. 单元测试#

  测试方案:创建一张用户表并预置一些数据,使用相同的SQL分别通过数据库查询和通过JSONSQL查询,断言两种方式查询的结果。

5.1 前期准备
Copy
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.util.List; /** * 用户信息 * * @author CL */ @Data @NoArgsConstructor @AllArgsConstructor @TableName(value = "test_user", autoResultMap = true) public class User { @TableId private Long id; @TableField private String name; @TableField private Integer age; @TableField private String sex; @TableField private String address; @TableField private Boolean status; @TableField(value = "createTime") private LocalDate createTime; @TableField(typeHandler = JacksonTypeHandler.class) private List<String> hobbies; @TableField(value = "luckyNumbers", typeHandler = JacksonTypeHandler.class) private List<Integer> luckyNumbers; }
Copy
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.c3stones.user.entity.User; import org.apache.ibatis.annotations.Mapper; /** * 用户Mapper * * @author CL */ @Mapper public interface UserMapper extends BaseMapper<User> { /** * 创建表 */ void createTable(); /** * 清除表 */ void dropTable(); }
Copy
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.c3stones.user.mapper.UserMapper"> <update id="createTable"> CREATE TABLE IF NOT EXISTS `test_user` ( `id` bigint NOT NULL, `name` varchar(20), `age` tinyint, `sex` varchar(1), `address` varchar(255), `status` tinyint, `createTime` datetime, `hobbies` json, `luckyNumbers` json, PRIMARY KEY (`id`) ); </update> <delete id="dropTable"> DROP TABLE IF EXISTS `test_user`; </delete> </mapper>
Copy
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/jql?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai username: root password: 123456
5.2 创建单元测试
Copy
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.alibaba.druid.DbType; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.c3stones.Application; import com.c3stones.db.mapper.DBMapper; import com.c3stones.user.entity.User; import com.c3stones.json.mapper.model.DataModel; import com.c3stones.json.parser.convert.JQLConvert; import com.c3stones.json.parser.select.JSONSelectFactory; import com.c3stones.user.mapper.UserMapper; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; /** * 测试SQL转JSONSQL并查询 * * @author CL */ @Slf4j @SpringBootTest(classes = Application.class) public class JQLMapperTest { @Resource private UserMapper userMapper; @Resource private DBMapper dbMapper; private static List<DataModel> dataList; /** * 构造查询SQL * * @return {@link Stream < Arguments >} */ private static Stream<Arguments> query() { return Stream.of( "select * from test_user", "select * from test_user where id = 1 ", "select * from test_user where id = 1 and name = '张三'", "select * from test_user where id = 2 or name = '张三'", "select * from test_user where name = '张三' and age > 28", "select * from test_user where id = 1 or name = '张三' and (age > 28 or sex = '男')", "select * from test_user where id = 1 and name = '张三' and (age > 28 or sex = '男') and (address like '%陕西%' and status != '0')", "select * from test_user where name in ('张三', '李四')", "select * from test_user where id = 1 and ( name in ('张三', '李四') and createTime between '2023-01-01' and '2023-04-30' )", "select * from test_user where ( name in ('张三', '李四') and age between 25 and 30 )", "select id, name from test_user where ( name in ('张三', '李四') and age between 26 and 30 )", "select id from test_user where 1 = 1", "select id from test_user where 1 != 1", "select id from test_user where name = 'XXX'", "select id from test_user where name is null", "select id from test_user where name is not null", "select hobbies from test_user where json_contains(hobbies, '[\"看书\"]')", "select * from test_user where json_contains(hobbies, '[\"看书\"]')", "select * from test_user where json_contains(hobbies, '[\"看书\"]') = 0", "select hobbies from test_user where json_length(hobbies) = 0 or hobbies is null", "select * from test_user where json_length(hobbies) > 0" ).map(Arguments::of); } /** * 构造聚合SQL * * @return {@link Stream<Arguments>} */ private static Stream<Arguments> aggregation() { return Stream.of( "select count(*) from test_user", "select count(*) as count from test_user", "select count(id) from test_user where id = 1", "select count(id) as count from test_user where id > 1", "select count(id) from test_user group by name", "select sex, count(id) as count from test_user group by sex", "select sex, count(*) as count from test_user group by sex order by count", "select sex, count(*) as count from test_user group by sex order by count desc", "select sex, count(*) as count from test_user group by sex order by count desc limit 1", "select sex, count(*) as count from test_user group by sex order by count desc limit 1, 1", "select sum(age), max(id), min(age), avg(id), count(age) from test_user", "select sex, sum(age), max(id), min(age), avg(age), count(*) from test_user where name != 'xxx' group by sex" ).map(Arguments::of); } /** * 预置表及数据 */ @BeforeEach public void before() { List<User> userList = new ArrayList<User>() {{ add(new User(1L, "张三", 25, "男", "陕西省西安市", true, LocalDate.of(2023, 1, 1), Stream.of("看书", "听歌").map(Objects::toString).collect(Collectors.toList()), Stream.of(7, 21).collect(Collectors.toList()))); add(new User(2L, "李四", 28, "女", "陕西省渭南市", true, LocalDate.of(2023, 4, 1), Stream.of("逛街", "购物").map(Objects::toString).collect(Collectors.toList()), Collections.emptyList())); add(new User(3L, "王五", 30, "男", "北京市", false, LocalDate.of(2023, 5, 1), Stream.of("看书").map(Objects::toString).collect(Collectors.toList()), Stream.of(8, 24).collect(Collectors.toList()))); }}; dataList = userList.stream().map(user -> new DataModel(JSONObject.parseObject(JSONUtil.toJsonStr(user)))).collect(Collectors.toList()); userMapper.createTable(); userMapper.delete(Wrappers.emptyWrapper()); userList.forEach(user -> userMapper.insert(user)); } /** * 清除表 */ @AfterEach public void after() { userMapper.dropTable(); } /** * 测试查询 * * @param sql SQL */ @ParameterizedTest @MethodSource("query") public void testQuery(String sql) { List<Map<String, Object>> dbResult = dbMapper.aggregation(sql); JQLConvert jqlConvert = new JQLConvert(JSONSelectFactory.any()); JSONMapperProvider provider = jqlConvert.convert(sql, DbType.mysql); provider.setDataList(dataList); JSONMapper jsonMapper = new JSONMapper(); List<Map<String, Object>> jqlResult = jsonMapper.query(provider); Assertions.assertEquals(dbResult.size(), jqlResult.size()); for (int i = 0; i < dbResult.size(); i++) { Map<String, Object> dbMap = dbResult.get(i); Map<String, Object> jqlMap = jqlResult.get(i); for (Map.Entry<String, Object> entry : dbMap.entrySet()) { Object expected = entry.getValue(); Object actual = jqlMap.get(entry.getKey()); if (expected instanceof LocalDateTime || actual instanceof Long) { expected = Date.from(LocalDateTimeUtil.parse(expected.toString()) .atZone(ZoneId.systemDefault()).toInstant()); actual = DateUtil.date(NumberUtil.parseLong(actual.toString())); } else if (expected instanceof String && actual instanceof Collection) { expected = ((String) expected).replaceAll(StrUtil.SPACE, StrUtil.EMPTY); actual = JSONObject.toJSONString(actual); } else if (expected instanceof Integer[] && actual instanceof Collection) { expected = JSONObject.toJSONString(expected); actual = JSONObject.toJSONString(actual); } else if (expected instanceof Number && actual instanceof Number) { expected = NumberUtil.parseDouble(expected.toString()); actual = NumberUtil.parseDouble(actual.toString()); } else if (expected instanceof Number && actual instanceof Boolean) { expected = BooleanUtil.toBoolean(expected.toString()); actual = BooleanUtil.toBoolean(actual.toString()); } Assertions.assertEquals(expected, actual); } } } /** * 测试聚合 * * @param sql SQL */ @ParameterizedTest @MethodSource(value = {"aggregation"}) public void testAggregation(String sql) { List<Map<String, Object>> dbResult = dbMapper.aggregation(sql); JQLConvert jqlConvert = new JQLConvert(JSONSelectFactory.any()); JSONMapperProvider provider = jqlConvert.convert(sql, DbType.mysql); provider.setDataList(dataList); JSONMapper jsonMapper = new JSONMapper(); List<Map<String, Object>> jqlResult = jsonMapper.aggregation(provider); Assertions.assertEquals(dbResult.size(), jqlResult.size()); for (Map<String, Object> dbMap : dbResult) { boolean match = jqlResult.stream().anyMatch(result -> dbMap.entrySet().stream().allMatch( entry -> { String key = entry.getKey(); if (StrUtil.contains(key, "(")) { key = StrUtil.subBefore(key, "(", false); } String v1 = StrUtil.toStringOrNull(entry.getValue()); String v2 = StrUtil.toStringOrNull(result.get(key)); if (NumberUtil.isNumber(v1) && NumberUtil.isNumber(v2)) { v1 = String.format("%.5f", NumberUtil.parseDouble(v1)); v2 = String.format("%.5f", NumberUtil.parseDouble(v2)); } return StrUtil.equals(v1, v2); })); Assertions.assertTrue(match); } } }

  控制台打印结果:

6. JavaScript中使用类SQL过滤JSON类型数据#

6.1 添加thymeleaf配置
Copy
spring: thymeleaf: prefix: classpath:/view/ suffix: .html encoding: UTF-8 servlet: content-type: text/html
6.2 创建页面
Copy
<!DOCTYPE html> <html> <head> <title>JSON-SQL</title> <script th:src="@{/jquery-2.2.4.min.js}"></script> <script th:src="@{/jsonsql-0.1.js}" data-main="home"></script> </head> <body> <H2 style="color: red">注意:where条件必须放到括号中只支持等于。</H2> <H3>请输入json数据:</H3> <textarea id="json" placeholder="请输入json数据" rows="10" cols="100"></textarea><br> <H3>请输入SQL:</H3> <textarea id="sql" placeholder="请输入SQL" rows="5" cols="100"></textarea><br> <H3><button type="button" onclick="handleClick()" style="width: 200px;height: 40px">获取结果</button></H3> <textarea id="result" rows="10" cols="100"></textarea><br> </body> <script type="text/javascript"> $(function() { $('#json').val("{\"data\":[" + "{\"id\":1,\"name\":\"张三\",\"age\":25,\"sex\":\"\",\"address\":\"陕西省西安市\",\"status\":true,\"createTime\":1672502400000,\"hobbies\":[\"看书\",\"听歌\"],\"luckyNumbers\":[7,21]}," + "{\"id\":2,\"name\":\"李四\",\"age\":28,\"sex\":\"\",\"address\":\"陕西省渭南市\",\"status\":true,\"createTime\":1680278400000,\"hobbies\":[\"逛街\",\"购物\"],\"luckyNumbers\":[]}," + "{\"id\":3,\"name\":\"王五\",\"age\":30,\"sex\":\"\",\"address\":\"北京市\",\"status\":false,\"createTime\":1682870400000,\"hobbies\":[\"看书\"],\"luckyNumbers\":[8,24]}" + "]}"); $('#sql').val("select * from json.data where (name=='张三' || name=='李四') order by age DESC limit 2"); }); function handleClick() { var json = eval("(" + $('#json').val() + ")"); var sql = $('#sql').val(); var result = jsonsql.query(sql, json); $('#result').val(JSON.stringify(result)); } </script> </html>
6.3 创建Controller
Copy
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; /** * 首页Controller * * @author CL */ @Controller public class IndexController { /** * 首页 * * @return */ @GetMapping(value = "index") public String index() { return "index"; } }
6.4 启动项目

  浏览器访问:http://127.0.0.1:8080/index

7. 项目地址#

spring-boot-json-sql-demo

posted @   C3Stones  阅读(1045)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
历史上的今天:
2020-06-01 Linux(CentOS7)安装Nginx(附简单配置)
点击右上角即可分享
微信分享提示
CONTENTS