JSQLParser 解析 复杂sql (表别名、字段与表对应关系)
作者:mmseoamin日期:2023-12-20

更新:

最新代码在这里:https://blog.csdn.net/m0_54892309/article/details/129615905

增加了对于嵌套SQL语句的解析,并改进了相关代码~~~

正文:

最近在搞一个公司自研的数据中台项目,许多模块都有解析sql的需求。于是乎,开发一个能完美解析sql语句的工具类已经是迫在眉睫了!

到网上百度了两下,便发现了JSQLParser这个免费好用的工具类,相信很多朋友早就在用了吧~~~

话不多说,先来了解下JSQLParser里的两个主要工具类吧。

工具类

功能

1

CCJSqlParserUtil

只能解析简单sql语句

2

CCJSqlParserManager

正确语法的sql都能解析

可以发现,CCJSqlParserUtil这个东西虽然简单好用功能强大精确无误(省略1000字),但是只能解析单表查询的简单sql,也就是说对于有子查询的sql是会直接报错的。

CCJSqlParserManager才是符合业务需求的真正好用的工具类,尽管它用起来确实麻烦,各种Expression表达式的解析,还有visit方法的重写,都是需要深刻理解才能用好的。

关于JSQLParser的基本语法网上都有,这里就不在赘述了。 在学习使用的过程中,我发现使用CCJSqlParserManager这个类去解析复杂sql时,无法正确解析出所有的表别名(也可能是我没理解到位...😅)。重写JSQLParser的visit方法应该可以实现表别名的解析,我这里就用自己比较能接受的方式来了。

实战环节:

  1. maven依赖

        
        
            com.github.jsqlparser
            jsqlparser
            4.4
        

  1. 实体类

NormalSqlStructureDto.class

    /** SQL语句 */
    private String sql;
    /** 表名 */
    private List tableNames;
    /** 检索项 */
    private List selectItems;
    /** 字段和表的映射关系 */
    private List colMappings;

ColMappingDto.class

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

  1. 主要代码

public class JsqlParserUtil {
    public static void main(String[] args) throws JSQLParserException {
        // 输入一个sql
        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";
        NormalSqlStructureDto normalSqlStructureDto = getStructure(sql.replace("\r", " ").replace("\n", " "), true);
        normalSqlStructureDto.getTableNames().forEach(System.out::println);
        System.out.println("===============================================");
        normalSqlStructureDto.getSelectItems().forEach(System.out::println);
        System.out.println("end");
    }
    /**
     * 构建表名和表别名的对应关系
     *
     * @param tableMapping
     * @param sql
     * @param tblAlias
     */
    private static void buildTblMapping(Map 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());
                }
            }
        }
    /**
     * 解析sql结构
     *
     * @param sql
     * @param isAlias true|false 是否使用别称
eg. 【s_id as id】 => 【id】
* @return * @throws ServiceException * @throws JSQLParserException */ public static NormalSqlStructureDto getStructure(String sql, boolean isAlias) throws ServiceException, JSQLParserException { NormalSqlStructureDto normalSqlStructureDto = new NormalSqlStructureDto(); if (StringUtils.isEmpty(sql)) { throw new ServiceException("请先输入SQL语句"); } normalSqlStructureDto.setSql(sql); sql = sql.replaceAll("(\\$\\{\\w*\\})|(\\{\\{\\w+\\}\\})", "''"); // 1.解析表名 CCJSqlParserManager parserManager = new CCJSqlParserManager(); // 解析SQL为Statement对象 Statement statement = parserManager.parse(new StringReader(sql)); // 创建表名发现者对象 TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); // 获取到表名列表 List tableNameList = tablesNamesFinder.getTableList(statement); normalSqlStructureDto.setTableNames(tableNameList); // 表别名映射 Map tableMapping = new HashMap<>(); tableNameList.forEach(i -> tableMapping.put(i, i)); // 字段和表的映射 List colMappingList = new ArrayList<>(); // 2.解析查询元素 列,函数等 Select select = (Select) CCJSqlParserUtil.parse(sql); PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); // FromItem fromItem = plainSelect.getFromItem(); // System.out.println(JSON.toJSON(fromItem).toString()); // fromItem.getAlias(); List selectItems = plainSelect.getSelectItems(); List 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(); // FIXME: 2023/3/9 Column col = ((Column) expression); Table colTbl = col.getTable(); if (Objects.nonNull(colTbl)) { tblAlias = colTbl.getName(); } buildTblMapping(tableMapping, sql, tblAlias); 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 = columnName.replace("'", ""); columnName = columnName.replace("\"", ""); columnName = columnName.replace("`", ""); } } } columnName = columnName.replace("'", ""); columnName = columnName.replace("\"", ""); columnName = columnName.replace("`", ""); colMapping.setName(col.getColumnName()); if (Objects.nonNull(alias) && StringUtils.isNotEmpty(alias.getName())) { colMapping.setAlias(alias.getName()); } colMapping.setTable(tableMapping.get(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)); } 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); colMapping.setType("varchar"); } } catch (Exception e) { columnName = selectItem.toString(); colMapping.setName(columnName); colMapping.setType("varchar"); colMapping.setTable(null); } columnList.add(columnName); colMappingList.add(colMapping); } normalSqlStructureDto.setSelectItems(columnList); normalSqlStructureDto.setColMappings(colMappingList); } return normalSqlStructureDto; } }

参考:https://blog.csdn.net/qq_41541619/article/details/104576427这篇博客,讲得非常详细。