更新:
最新代码在这里: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方法应该可以实现表别名的解析,我这里就用自己比较能接受的方式来了。
实战环节:
maven依赖
com.github.jsqlparserjsqlparser4.4
实体类
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;
主要代码
public class JsqlParserUtil {public static void main(String[] args) throws JSQLParserException {// 输入一个sqlString 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/9Column 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这篇博客,讲得非常详细。