更新:

最新代码在这里: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时,无法正确解析出所有的表别名(也可能是我没理解到位...😅)。重写JSQLParservisit方法应该可以实现表别名的解析,我这里就用自己比较能接受的方式来了。

实战环节:

  1. maven依赖

        <!-- sql解析 工具 jsqlparser -->
        <dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>4.4</version>
        </dependency>

  1. 实体类

NormalSqlStructureDto.class

    /** SQL语句 */
    private String sql;
    /** 表名 */
    private List<String> tableNames;
    /** 检索项 */
    private List<String> selectItems;
    /** 字段和表的映射关系 */
    private List<ColMappingDto> 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<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());
                }
            }
        }

    /**
     * 解析sql结构
     *
     * @param sql
     * @param isAlias true|false 是否使用别称<br> eg. 【s_id as id】 => 【id】<br>
     * @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<String> tableNameList = tablesNamesFinder.getTableList(statement);
        normalSqlStructureDto.setTableNames(tableNameList);
        // 表别名映射
        Map<String, Object> tableMapping = new HashMap<>();
        tableNameList.forEach(i -> tableMapping.put(i, i));
        // 字段和表的映射
        List<ColMappingDto> 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<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();
                        // 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这篇博客,讲得非常详细。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐