JSQLParser 解析复杂SQL 2.0

前言

这段时间,为了开发数据中台项目,我去研究学习了JSQLParser(Java中解析SQL语句的工具),并且结合网上资料,写了一个初步的SQL解析工具类...

正文

时隔三天,我又回来了, 因为之前JSQLParser的使用方式果然不太正确😅 。基本上正常的SQL通过JSQLParser都可以完美解析出它的结构。

主要错误如下:

  1. 表别名的解析是包含from和join两部分的,之前只解析了from,所以无法获得所有的表。

  1. SQL是可以不断嵌套的,解析的时候必须判断是否含有子查询,然后将子查询的SQL语句递归解析。

  1. 没有对union的情况做判断 2023/5/12.

关于字段类型的解析,可以用jdbc查询返回的map,然后判断instanceof的字段类型,非常简单。

这里就贴下最新的代码吧,目前已经支持了嵌套SQL的解析~~

  1. 实体类:

NormalSqlStructureDto.class


   /**
     * SQL语句
     */
    private String sql;
    /**
     * 表名
     */
    private List<String> tableNames;
    /**
     * 检索项
     */
    private List<String> selectItems;
    /**
     * 字段和表的映射关系
     */
    private List<ColMappingDto> colMappings;
    /**
     * 表别名映射
     */
    private Map<String, Object> tableAliasMapping;

ColMappingDto.class


 /** 字段名 */
    private String name;
    /** 字段别名 */
    private String alias;
    /** 关联表 */
    private Object table;
    /** 表别名 */
    private Object tableAlias;
    /** 字段类型 */
    private String type;

  1. 主要实现类:


import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.SimpleNode;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.StringReader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * SQL解析通用工具
 *
 * @author xurenyi
 * @since 2023/3/17 11:19
 **/
public class JsqlParserUtil {
    /**
     * 表名列表
     */
    private final static ThreadLocal<List<String>> TABLE_NAME_LIST = new ThreadLocal<>();
    /**
     * 查询字段名列表
     */
    private final static ThreadLocal<List<String>> COLUMN_NAME_LIST = new ThreadLocal<>();
    /**
     * 表别名映射关系
     */
    private final static ThreadLocal<Map<String, Object>> TABLE_AND_ALIAS_MAPPING = new ThreadLocal<>();
    /**
     * 字段映射关系列表
     */
    private final static ThreadLocal<List<ColMappingDto>> COL_MAPPING_LIST = new ThreadLocal<>();

    private static Logger logger = LoggerFactory.getLogger("JsqlParserUtil");

    public static void main(String[] args) throws JSQLParserException {
        //1、获取原始sql输入
//        String sql = "select t1.s_id as id," +
//                "t1.s_name," +
//                "t1.join_date, \n" +
//                "t2.score, \n" +
//                "t2.* \n" +
//                "from schema1.edu_college_student t1\n" +
//                "join schema2.edu_college_test_score t2\n" +
//                "on t2.s_id = t1.s_id \n" +
//                "where 1=1 \n";

//        String sql = "select t11.*,t1.* \n" +
//                "from original_data.edu_college_student As t1\n" +
//                "JOIN original_data.edu_college_test_score t11\n" +
//                "on t1.s_id = t11.s_id \n" +
//                "where 1=1 \n";

//        String sql = "select t1.*,t1.*,t2.*\n" +
//                "from edu_college_student t1\n" +
//                "join edu_college_test_score t2 on t2.s_id = t1.s_id";

//        String sql = "select '1' from meta_dict_type";

//        String sql = "select\n" +
//                "  t1.s_id,\n" +
//                "  t1.s_name,\n" +
//                "  max(t2.score) as maxscore,\n" +
//                "  t2.course\n" +
//                "from\n" +
//                "  original_data.edu_college_student t1\n" +
//                "  join original_data.edu_college_test_score t2 on t2.s_id = t1.s_id\n" +
//                "group by\n" +
//                "  t2.course,\n" +
//                "  t1.s_id,\n" +
//                "  t1.s_name";

//        String sql = "select t2.id from (select t1.id from (select id from original_data.edu_college_student) t1) t2";

//        String sql = "select t1.stime,t1.sscore from (select o.create_time as stime,t.score as sscore from original_data.edu_college_student o join original_data.edu_college_test_score t on t.s_id = o.s_id ) t1";

//        String sql = "select t1.s_id as sid, t1.t1.s_name from original_data.edu_college_student t1";

//        String sql = "select\n" +
//                "  v1.id as t_id,\n" +
//                "  v1.s_name as t_s_name,\n" +
//                "  v1.join_date as t_date,\n" +
//                "  v1.score As t_score,\n" +
//                "  t3.course AS t_course\n" +
//                "from\n" +
//                "  (\n" +
//                "    select\n" +
//                "      t1.s_id as id,\n" +
//                "      t1.s_name,\n" +
//                "      t1.join_date,\n" +
//                "      t2.score\n" +
//                "    from\n" +
//                "      original_data.edu_college_student t1\n" +
//                "      join original_data.edu_college_test_score t2 on t2.s_id = t1.s_id\n" +
//                "    where\n" +
//                "      1 = 1\n" +
//                "  ) v1\n" +
//                "  join original_data.edu_college_sign_in_situation t3 on t3.s_id = v1.id\n" +
//                "  limit 10";

        String sql = "select '正常签到' as '签到情况',sum(1) as '次数' from `original_data`.hr_attendance_summary_day where is_early = 0 and is_later = 0 \n" +
                "union all \n" +
                "SELECT '迟到' AS '签到情况',if(SUM(is_later)is null,0,SUM(is_later)) AS '次数' FROM `original_data`.hr_attendance_summary_day WHERE is_later = 1\n" +
                "union all \n" +
                "SELECT '早退' AS '签到情况',if(SUM(is_early)is null,0,SUM(is_early)) AS '次数' FROM `original_data`.hr_attendance_summary_day WHERE is_early = 1";

        try {
            getStructure(sql.replaceAll("[\r\n]", " "), true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取SQL结构
     *
     * @param sql SQL语句
     * @throws JSQLParserException 解析异常
     */
    public static NormalSqlStructureDto getStructure(String sql, boolean isAlias) throws JSQLParserException {
        //logger.info("【START】");
        TABLE_NAME_LIST.set(new ArrayList<>());
        COLUMN_NAME_LIST.set(new ArrayList<>());
        TABLE_AND_ALIAS_MAPPING.set(new HashMap<>());
        COL_MAPPING_LIST.set(new ArrayList<>());
        NormalSqlStructureDto normalSqlStructureDto = new NormalSqlStructureDto();
        if (StringUtils.isEmpty(sql)) {
            throw new ServiceException("请先输入SQL语句");
        }
        normalSqlStructureDto.setSql(sql);
        sql = sql.replaceAll("(\\$\\{\\w*\\})|(\\{\\{\\w+\\}\\})", "''");
        analysisSql(sql, isAlias, false);
        normalSqlStructureDto.setSelectItems(COLUMN_NAME_LIST.get());
        normalSqlStructureDto.setTableNames(TABLE_NAME_LIST.get());
        normalSqlStructureDto.setTableAliasMapping(TABLE_AND_ALIAS_MAPPING.get());
        List<ColMappingDto> colMappingDtoList = COL_MAPPING_LIST.get();
        for (ColMappingDto mapping : colMappingDtoList) {
            if (Objects.isNull(mapping.getTable()) && Objects.nonNull(mapping.getTableAlias())) {
                mapping.setTable(TABLE_AND_ALIAS_MAPPING.get().get(mapping.getTableAlias()));
            }
        }
        normalSqlStructureDto.setColMappings(colMappingDtoList);

        //logger.info("【END】");
        return normalSqlStructureDto;
    }

    /**
     * 解析SQL
     *
     * @param sql         SQL语句
     * @param isAlias     true|false 是否使用别称<br> eg. 【s_id as id】 => 【id】<br>
     * @param isSubSelect 是否是子查询
     * @throws JSQLParserException 解析异常
     */
    private static void analysisSql(String sql, boolean isAlias, boolean isSubSelect) throws JSQLParserException {
        //logger.info("是否是子查询: " + isSubSelect);
        CCJSqlParserManager parserManager = new CCJSqlParserManager();
        // 解析SQL为Statement对象
        Statement statement = parserManager.parse(new StringReader(sql));
        Select select = (Select) CCJSqlParserUtil.parse(sql);
        SelectBody selectBody = select.getSelectBody();
        // 判断是否是union查询
        if(selectBody instanceof SetOperationList){
            SetOperationList operationList = (SetOperationList) select.getSelectBody();
            List<SelectBody> plainSelects = operationList.getSelects();
            for (SelectBody plainSelect : plainSelects) {
                analysisSql(plainSelect.toString(), isAlias, isSubSelect);
            }
        } else if(selectBody instanceof PlainSelect){
            analysisSelectBody(isAlias, isSubSelect, statement, select);
        }
    }

    /**
     * 解析SelectBody
     *
     * @param isAlias true|false 是否使用别称<br> eg. 【s_id as id】 => 【id】<br>
     * @param isSubSelect 是否是子查询
     * @param statement Statement对象
     * @param select Select对象
     * @throws JSQLParserException 解析异常
     */
    private static void analysisSelectBody(boolean isAlias, boolean isSubSelect, Statement statement, Select select) throws JSQLParserException {
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        // 1.解析表名
        List<String> tableNameList = getTable(statement);
        // 表别名映射
        Map<String, Object> tableMapping = new HashMap<>();
        tableNameList.forEach(i -> tableMapping.put(i, i));
        if (CollectionUtils.isEmpty(TABLE_AND_ALIAS_MAPPING.get())) {
            TABLE_AND_ALIAS_MAPPING.get().putAll(tableMapping);
        }
        if (CollectionUtils.isEmpty(TABLE_NAME_LIST.get())) {
            TABLE_NAME_LIST.get().addAll(tableNameList);
        }
        // 字段和表的映射
        List<ColMappingDto> colMappingList = new ArrayList<>();

        // 2.解析查询元素 列,函数等
        getSelectItems(plainSelect, tableNameList, tableMapping, colMappingList, isAlias, isSubSelect);

        // 3.解析from(可能含有子查询)
        FromItem fromItem = plainSelect.getFromItem();
        String fromTable = getFromItem(fromItem, isAlias);
        //logger.info("from 表名:" + fromTable);

        // 4.解析join
        List<Join> tablewithjoin = getJoinItem(plainSelect);
        if (!CollectionUtils.isEmpty(tablewithjoin)) {
            tablewithjoin.forEach(i -> System.out.println("连接方式为:" + i));
        }
    }

    /**
     * 获取join的项目
     *
     * @param plainSelect
     * @return
     */
    private static List<Join> getJoinItem(PlainSelect plainSelect) {
        // 如果关联后面是子查询,可以通过遍历join集合,获取FromItem rightItem = join.getRightItem();
        List<Join> joinList = plainSelect.getJoins();
        if (joinList != null) {
            for (int i = 0; i < joinList.size(); i++) {
                //注意 , leftjoin rightjoin 等等的to string()区别
                Join join = joinList.get(i);
                String alias = join.getRightItem().getAlias().toString().trim();
                String tableName = join.getRightItem().toString().replaceAll("(?i)\\s+as\\s+", " ").replace(alias, "").trim();
                //logger.info("join 表名:" + join.getRightItem().toString());
                //logger.info("物理名:" + tableName);
                //logger.info("别名:" + alias);
                TABLE_AND_ALIAS_MAPPING.get().put(alias, tableName);
            }
        }
        return joinList;
    }

    /**
     * 获取from的项目
     *
     * @param fromItem
     * @return
     * @throws JSQLParserException 解析异常
     */
    private static String getFromItem(FromItem fromItem, boolean isAlias) throws JSQLParserException {
        // 判断fromItem属于哪种类型,如果是subSelect类型就是子查询
        if (fromItem instanceof SubSelect) {
            //logger.info("-----------------子查询开始-----------------");
            SelectBody selectBody = ((SubSelect) fromItem).getSelectBody();
            //logger.info("子查询" + selectBody.toString());
            analysisSql(selectBody.toString(), true, true);
            //logger.info("-----------------子查询结束-----------------");
        }
        String alias = "";
        try {
            alias = fromItem.getAlias().toString().trim();
            Table table = ((Table) fromItem);
            String tableName = table.getName();
            String schemaName = table.getSchemaName();
            schemaName = StringUtils.isEmpty(schemaName) ? "" : schemaName;
            String name = schemaName + "." + tableName;
            //logger.info("物理名:" + name);
            //logger.info("别名:" + alias);

            TABLE_AND_ALIAS_MAPPING.get().put(alias, name);
        } catch (Exception e) {
            if (StringUtils.isNotEmpty(alias)) {
                TABLE_AND_ALIAS_MAPPING.get().put(alias, fromItem.toString());
            }
        } finally {
            return fromItem.toString();
        }

    }

    /**
     * 获取当前查询字段
     *
     * @param plainSelect
     * @param tableNameList
     * @param tableMapping
     * @param colMappingList
     */
    private static void getSelectItems(PlainSelect plainSelect, List<String> tableNameList, Map<String, Object> tableMapping, List<ColMappingDto> colMappingList, boolean isAlias, boolean isSubSelect) {
        // 目前不解析子查询
        if (isSubSelect) {
            return;
        }
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        List<String> columnList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(selectItems)) {
            for (SelectItem selectItem : selectItems) {
                ColMappingDto colMapping = new ColMappingDto();
                // 字段名称
                String columnName = "";
                // 表别名
                String tblAlias = "";
                try {
                    if (selectItem instanceof SelectExpressionItem) {
                        SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
                        Alias alias = selectExpressionItem.getAlias();
                        Expression expression = selectExpressionItem.getExpression();
                        Column col = ((Column) expression);
                        Table colTbl = col.getTable();
                        if (Objects.nonNull(colTbl)) {
                            tblAlias = colTbl.getName();
                        }
                        if (!isAlias) {
                            columnName = selectItem.toString();
                        } else if (expression instanceof CaseExpression) {
                            // case表达式
                            columnName = alias.getName();
                        } else if (expression instanceof LongValue || expression instanceof StringValue || expression instanceof DateValue || expression instanceof DoubleValue) {
                            // 值表达式
                            columnName = Objects.nonNull(alias.getName()) ? alias.getName() : expression.getASTNode().jjtGetValue().toString();
                        } else if (expression instanceof TimeKeyExpression) {
                            // 日期
                            columnName = alias.getName();
                        } else {
                            if (alias != null) {
                                columnName = alias.getName();
                            } else {
                                SimpleNode node = expression.getASTNode();
                                Object value = node.jjtGetValue();
                                if (value instanceof Column) {
                                    columnName = ((Column) value).getColumnName();
                                } else if (value instanceof Function) {
                                    columnName = value.toString();
                                } else {
                                    // 增加对select 'aaa' from table; 的支持
                                    columnName = String.valueOf(value);
                                    columnName = getString(columnName);
                                }
                            }
                        }

                        columnName = getString(columnName);

                        colMapping.setName(col.getColumnName());
                        if (Objects.nonNull(alias) && StringUtils.isNotEmpty(alias.getName())) {
                            colMapping.setAlias(alias.getName());
                        }
//                        colMapping.setTable(tableMapping.get(tblAlias));
                        colMapping.setTableAlias(tblAlias);

                    } else if (selectItem instanceof AllTableColumns) {
                        AllTableColumns allTableColumns = (AllTableColumns) selectItem;
                        columnName = allTableColumns.toString();
                        if (columnName.indexOf(".") > -1) {
                            tblAlias = columnName.substring(0, columnName.indexOf(".")).trim();
//                            buildTblMapping(tableMapping, sql, tblAlias);
//                            colMapping.setTable(tableMapping.get(tblAlias));
                            colMapping.setTableAlias(tblAlias);
                        } else {
                            colMapping.setTable(tableNameList);
                        }
                        colMapping.setName(columnName);
                    } else if (selectItem.toString().equals("*")) {
                        columnName = selectItem.toString();
                        colMapping.setName(columnName);
                        colMapping.setTable(tableNameList);
                    } else {
                        columnName = selectItem.toString();
                        colMapping.setName(columnName);
                    }
                } catch (Exception e) {
                    columnName = selectItem.toString();
                    colMapping.setName(columnName);
                    colMapping.setTable(null);
                    if (columnName.matches("(?i).+\\s+as\\s+.+")) {
                        colMapping.setAlias(columnName.replaceAll("(?i).+\\s+as\\s+", "").trim());
                    }
                }

                columnList.add(columnName);
                colMappingList.add(colMapping);
                if (!isSubSelect) {
                    COL_MAPPING_LIST.get().add(colMapping);
                    COLUMN_NAME_LIST.set(columnList);
                }
            }
            //logger.info("查询字段名:" + columnList.toString());
        }
    }

    private static String getString(String columnName) {
        columnName = columnName.replace("'", "");
        columnName = columnName.replace("\"", "");
        columnName = columnName.replace("`", "");
        return columnName;
    }

    /**
     * 获取SQL中所有出现的表
     *
     * @param statement
     * @return
     */
    private static List<String> getTable(Statement statement) {
        // 创建表名发现者对象
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        // 获取到表名列表
        List<String> tableNameList = tablesNamesFinder.getTableList(statement);
        //logger.info("查询表名:" + tableNameList.toString());

        return tableNameList;
    }

    /**
     * 构建表名和表别名的对应关系
     *
     * @param tableMapping
     * @param sql
     * @param tblAlias
     */
    private static void buildTblMapping(Map<String, Object> tableMapping, String sql, String tblAlias) {
        if (StringUtils.isNotEmpty(tblAlias)) {
            if (CollectionUtils.isEmpty(tableMapping) || Objects.isNull(tableMapping.get(tblAlias))) {
                sql = sql.replaceAll("(?i)\\s+as\\s+", " ");
                String regex = "(from|join)\\s+(\\w+\\.)?\\w+\\s+".concat(tblAlias).concat("\\s?");
                Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
                Matcher m = p.matcher(sql.replaceAll("[\n\r]", " "));
                String replaceReg = "(?i)(from|join|" + tblAlias + ")";
                while (m.find()) {
                    tableMapping.put(tblAlias, m.group(0).replaceAll(replaceReg, "").trim());
                }
            }
        }
    }
}

  1. 解析结果展示

结论

JSQLParser是Java里用来解析SQL结构的一个非常好用的工具,有且不仅限于上面的功能。

  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
以下是使用jsqlparser解析子查询sql的示例代码: ```java import java.io.StringReader; import java.util.List; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SubSelect; public class JSqlParserDemo { public static void main(String[] args) throws JSQLParserException { String sql = "SELECT * FROM (SELECT * FROM table1 WHERE id IN (SELECT id FROM table2)) AS subquery"; Statement statement = CCJSqlParserUtil.parse(sql); Select selectStatement = (Select) statement; PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody(); SubSelect subSelect = (SubSelect) plainSelect.getFromItem(); Select subquery = (Select) subSelect.getSelectBody(); PlainSelect subqueryPlainSelect = (PlainSelect) subquery.getSelectBody(); List<Expression> expressions = subqueryPlainSelect.getSelectItems(); for (Expression expression : expressions) { System.out.println(expression.toString()); } } } ``` 上述代码中,我们首先将子查询sql语句作为字符串传入JSqlParser进行解析。然后,我们通过获取Select对象和PlainSelect对象来获取子查询的Select对象和PlainSelect对象。最后,我们可以通过获取子查询的PlainSelect对象来获取子查询中的Select字段。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值