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

news2024/11/23 13:21:21

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
<?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 创建枚举类
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;
    }

}
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;
    }

}
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;
    }

}
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结构化实体)
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);
    }

}
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);
    }

}
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);
    }

}
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);
    }

}
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);
    }

}
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);
    }

}
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);
    }

}
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默认查询语法接口
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字符串查询语法
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数字查询语法
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查询语法处理工厂
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转换类
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异常类
/**
 * 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();
    }

}
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断言类
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
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 创建启动类
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 前期准备
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;

}
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();

}
<?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>
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 创建单元测试
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配置
spring:
  thymeleaf:
    prefix: classpath:/view/
    suffix: .html
    encoding: UTF-8
    servlet:
      content-type: text/html
6.2 创建页面
<!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
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/600234.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python内置变量:掌握这些变量,让你的编程更高效

Python内置变量&#xff1a;掌握这些变量&#xff0c;让你的编程更高效 Python作为一门优秀的编程语言&#xff0c;自然不会缺少重要的内置变量。这些内置变量可以帮助程序员轻松地实现各种编程功能&#xff0c;提高编程效率。在本文中&#xff0c;我们将介绍Python内置变量的…

chatgpt赋能python:Python内置函数使用指南

Python内置函数使用指南 Python是一种高级编程语言&#xff0c;得益于其简单易学的语法、强大的标准库和丰富的第三方模块&#xff0c;现已成为全球最受欢迎的编程语言之一。其中&#xff0c;Python内置函数是Python编程的重要组成部分&#xff0c;本文将为您介绍这些内置函数…

四种主要的IO模型

基本概念 基本概念阻塞IO指的是需要内核IO操作彻底完成后&#xff0c;才返回到用户空间执行用户的操作。阻塞指的是用户空间程序的执行状态。传统的IO模型都是同步阻塞IO。在Java中&#xff0c;默认创建的socket都是阻塞的。简单来说&#xff1a;阻塞是指用户空间&#xff08…

尚硅谷-云尚办公-项目复盘

尚硅谷-云尚办公-项目复盘 资料地址本文介绍问题汇总问题1.knife4j无法下载 视频4问题2.dev等含义 视频5问题3.wrapper继承/实现图 视频8问题4.修改统一返回结果 视频11问题5.修改后新增也变修改 视频29问题6.redis中key值乱码 视频55-60问题7.RangeError: Maximum call stack …

高完整性系统工程(六):INTRODUCING ADA

目录 1. ADA的历史 2. ADA的特点 2.1 Strong, Static Typing 强语言、强静态类型语言 2.1.1 ADA is Strong, Static Typing 2.1.2 C is Weak, Static Typing 2.2 Module System 2.3 Portable 2.3.1 ADA 2.3.2 C 2.3.3 Cost of Runtime Checking 2.4 Readability …

IPython使用学习笔记

学习《利用python进行数据分析》第三章 IPython:一种交互式计算和开发环境的笔记&#xff0c;共享给大家&#xff0c;同时为自己作为备忘用。 安装ipython用pip即可。ps.博主用的是win7系统&#xff0c;所以接下来的都是在windows系统下操作的。 一.Ipython基础 启动&#xff…

chatgpt赋能python:Python关闭程序代码的实现方法

Python 关闭程序代码的实现方法 如果你是一个经验丰富的 Python 工程师&#xff0c;你应该知道如何在程序中实现正常关闭。不过&#xff0c;如果你是一个新手&#xff0c;这可能会变得有点棘手&#xff0c;特别是当你需要在程序中添加一些特定的关闭功能的时候。本文将为您介绍…

【javaEE】计算机网络原理初始

目录 1、网络发展史 1.1、独立模式 1.2、网络互连 1.2.1、局域网&#xff08;LAN&#xff09; 1.2.2、广域网&#xff08;WAN&#xff09; 1.2.3、广域网和局域网的区别 1.2.4、局域网组建网络的方式 &#xff08;了解&#xff09; &#xff12;、网络通信基础 2.1、I…

前端开发技术栈(工具篇):2023最新版nvm的Win/Linux安装和使用(详细) 27.8k stars

目录 nvm是什么 nvm下载 nvm安装 Windows nvm的使用 安装Node.js 切换Node.js版本 卸载Node.js 其他使用方法 Linux nvm的使用 安装NVM 使用NVM 总结 Node.js是一个非常流行的JavaScript运行时环境&#xff0c;可以帮助开发人员构建高性能的网络应用程序, 它被用于…

SpringCloud:分布式锁和线程安全

这篇文章是一个初步了解分布式应用的线程安全和锁的文章&#xff0c;所有截图及代码全部来自亲身实践 1.对于单机应用我们可以把锁加在方法维度&#xff08;有用&#xff0c;不推荐&#xff09; 像这样 但是我们应该缩小锁的范围&#xff0c;我们这里是在派单&#xff0c;避免…

手撕希尔排序

什么是希尔排序&#xff1f;他的效率怎摸样&#xff0c;如何去实现希尔排序呢&#xff1f;在这之前可能我们已经了解了希尔排序&#xff0c;作为排序中的老大哥一员&#xff0c;希尔排序的效率也是屈指可数的。 想要知道希尔排序如何实现我们就的先了解插入排序。 目录 1.何…

Flutter 笔记 | Flutter 核心原理(六)Embedder 启动流程(Android)

Embedder是Flutter接入原生平台的关键&#xff0c;其位于整个Flutter架构的底层&#xff0c;负责Engine的创建、管理与销毁&#xff0c;同时也为Engine提供绘制UI的接口&#xff0c;那么底层的实现细节如何&#xff1f;本文将详细分析。 Embedder关键类分析 在正式分析Embedd…

chatgpt赋能python:Python知识|关联两个列表

Python 知识 | 关联两个列表 Python 是一种高效的编程语言&#xff0c;它能够很好地进行数据处理&#xff0c;因此在 SEO 领域得到广泛的应用。关联两个列表是一种基础的数据处理方法&#xff0c;本文将为读者详细介绍如何使用 Python 关联两个列表&#xff0c;并给出一些实例…

Rust每日一练(Leetday0018) N皇后II、最大子数组和、螺旋矩阵

目录 52. N皇后 II N Queens II &#x1f31f;&#x1f31f;&#x1f31f; 53. 最大子数组和 Maximum Subarray &#x1f31f;&#x1f31f; 54. 螺旋矩阵 Spiral Matrix &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏…

chatgpt赋能python:Python关键词匹配:优化你的SEO策略

Python关键词匹配&#xff1a;优化你的SEO策略 在当今数字时代&#xff0c;搜索引擎是许多人获取信息和发现新客户的主要渠道。对于企业或个人网站来说&#xff0c;优化SEO&#xff08;搜索引擎优化&#xff09;策略变得至关重要。在SEO的世界里&#xff0c;关键词匹配是一个重…

springboot+vue编程训练考试测试系统设计与实现

本编程训练系统管理员功能有管理员和用户。管理员功能有个人中心&#xff0c;用户管理&#xff0c;题库资源管理&#xff0c;用户交流&#xff0c;试卷管理&#xff0c;留言板管理&#xff0c;试题管理&#xff0c;系统管理&#xff0c;考试管理。用户可以查看题库资源&#xf…

chatgpt赋能python:使用Python进行人民币兑换-带着您深入了解

使用Python进行人民币兑换 - 带着您深入了解 在当今日益全球化的世界里&#xff0c;进行货币兑换已成为很正常的事情。人民币是世界上最常用的货币之一&#xff0c;而Python作为一种强大的编程语言&#xff0c;可以帮助我们进行人民币兑换计算。本文将介绍如何使用Python进行人…

使用CCProxy搭建windows系统阿里云socket代理服务器 教程

目录 1. 通过windows远程连接阿里云服务器2. 云服务器上安装CCProxy2.1 CCProxy下载安装2.2 设置协议、代理服务、端口号和ip2.3 新建代理用户2.4 确保你的CCProxy启动了服务 3. 在阿里云实例安全组中开放代理端口3.1 前往安全组页面3.2 添加你对应服务的开放端口 总结 欢迎关注…

Java键盘事件处理及监听机制解析

文章目录 概念KeyEventKeyListener代码演示总结 概念 Java事件处理采用了委派事件模型。在这个模型中&#xff0c;当事件发生时&#xff0c;产生事件的对象将事件信息传递给事件的监听者进行处理。在Java中&#xff0c;事件源是产生事件的对象&#xff0c;比如窗口、按钮等&am…

java企业级信息系统开发学习笔记11 利用MyBatis实现条件查询

文章目录 一、学习目标1.对学生表进行条件查询&#xff0c;涉及姓名、性别和年龄三个字段。2.比如查询姓“吴”&#xff0c;性别为“女”&#xff0c;同时年龄为19的学生记录 二、打开上一笔记mybatis项目三、对学生表实现条件查询&#xff08;一&#xff09;创建学生映射器配置…