更新:
最新代码在这里: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依赖
<!-- sql解析 工具 jsqlparser -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.4</version>
</dependency>
实体类
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;
主要代码文章来源:https://www.toymoban.com/news/detail-430503.html
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这篇博客,讲得非常详细。文章来源地址https://www.toymoban.com/news/detail-430503.html
到了这里,关于JSQLParser 解析 复杂sql (表别名、字段与表对应关系)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!