场景

前台传入的所有sql语句,都需要对表名做解析,添加前后缀。
一开始考虑使用正则匹配,但是sql语句表名的场景太多,select、join都会有表名。最后确定使用Druid中的Vistor来解析SQL语句
Druid中主要使用访问者模式来解析sql,并且封装了对sql语句解析生成的sql树的一系列操作。
访问者模式,是行为型设计模式之一。访问者模式是一种将数据操作与数据结构分离的设计模式。
Druid中默认已经支持标准sql,只要看一下通用的sql实现方式即可。

实现

1.添加依赖

<dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.22</version>
            </dependency>

2.代码开发

Druid的accept方法会调用visitor,看了SQLASTVisitor,里面默认实现了上面那些对于ast语法树操作的接口,没有符合我需求的,需要自己实现,只要继承了这个类以后,重写visitor方法就行。大致代码如下

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.alibaba.druid.util.JdbcConstants;

import java.util.List;

/**
 * @Description TsqlTablePrefixVisitor
 * @Author lcc
 * @Date 2021/6/22
 */
public class TsqlTablePrefixVisitor extends MySqlASTVisitorAdapter {
    private final static String TABLE_PREFIX_DS = "ods_ds_";
    private final static String TABLE_SUFFIX_DS = "_all";
    private final static String dbType = JdbcConstants.CLICKHOUSE;
    
    @Override
    public boolean visit(SQLExprTableSource x) {
        String table = x.getExpr().toString();
        if (table.contains("`")){
            table = table.replace("`","");
        }
       // x.setExpr(TABLE_PREFIX_DS+table+TABLE_SUFFIX_DS);
        x.setExpr(table+TABLE_SUFFIX_DS);

        return true;
    }
  
    public static void main(String[] args) {
        String chsql = "";
        TsqlTablePrefixVisitor prefixVisitor = new TsqlTablePrefixVisitor();
        List<SQLStatement> stmtList = SQLUtils.parseStatements(chsql, dbType);
        for (SQLStatement stmt : stmtList) {
            stmt.accept(prefixVisitor);
        }
        String convertedSql = SQLUtils.toSQLString(stmtList, dbType);
        System.out.println(convertedSql);
    }
}

我重写的visit()方法主要实现两个功能:
1.去除表名的反引号,因为有时候传入的表名会是`tablename`样式。
2.将SQL语句中的所有表名都添加前后缀。

测试

我这里用一个复杂的SQL语句做演示,SQL语句中包括join和子查询等。

select t12.serviceCode as logistics_id, t5.entityName as logistics_name, t12.fulfillmentSite as delivery_warehouse_name, t7.itemCode as item_code, t7.itemName as item_name, t10.brandName as brand_name, CONCAT_WS('/',t8.item_category_level1_name,t8.item_category_level2_name,t8.item_category_level3_name,t8.item_category_level4_name) as categroy_name, t7.year as item_year, t7.season as item_season, substr(t7.launchDate,1,10) as launch_date, t6.skuCode as sku_code, coalesce(if(JSON_EXTRACT(t6.attributes, '$[0].attrKey') like '%颜色%', JSON_EXTRACT(t6.attributes, '$[0].attrVal'), if(JSON_EXTRACT(t6.attributes, '$[1].attrKey') like '%颜色%', JSON_EXTRACT(t6.attributes, '$[1].attrVal'), null))) as sku_color, coalesce(if(JSON_EXTRACT(t6.attributes, '$[1].attrKey') like '%尺码%', JSON_EXTRACT(t6.attributes, '$[1].attrVal'), if(JSON_EXTRACT(t6.attributes, '$[0].attrKey') like '%尺码%', JSON_EXTRACT(t6.attributes, '$[0].attrVal'), null))) as sku_size, t3.tradeQty as item_cnt, t6.salePrc_value as sku_price from trade__trade_base_order_line_b_o t3 left join  item__sku_b_o t6 on JSON_EXTRACT(t3.sku,'$.id') = t6.id left join item__item_b_o t7 on t6.item = t7.id left join ( SELECT CASE WHEN t1.categoryLevel = 1 THEN t1.id WHEN t1.categoryLevel = 2 THEN t2.id WHEN t1.categoryLevel = 3 THEN t3.id WHEN t1.categoryLevel = 4 THEN t4.id ELSE '' END AS item_category_level1_id, CASE WHEN t1.categoryLevel = 1 THEN t1.categoryName WHEN t1.categoryLevel = 2 THEN t2.categoryName WHEN t1.categoryLevel = 3 THEN t3.categoryName WHEN t1.categoryLevel = 4 THEN t4.categoryName ELSE '' END AS item_category_level1_name, CASE WHEN t1.categoryLevel = 2 THEN t1.id WHEN t1.categoryLevel = 3 THEN t2.id WHEN t1.categoryLevel = 4 THEN t3.id ELSE '' END AS item_category_level2_id, CASE WHEN t1.categoryLevel = 2 THEN t1.categoryName WHEN t1.categoryLevel = 3 THEN t2.categoryName WHEN t1.categoryLevel = 4 THEN t3.categoryName ELSE '' END AS item_category_level2_name, CASE WHEN t1.categoryLevel = 3 THEN t1.id WHEN t1.categoryLevel = 4 THEN t2.id ELSE '' END AS item_category_level3_id, CASE WHEN t1.categoryLevel = 3 THEN t1.categoryName WHEN t1.categoryLevel = 4 THEN t2.categoryName ELSE '' END AS item_category_level3_name, CASE WHEN t1.categoryLevel = 4 THEN t1.id ELSE '' END AS item_category_level4_id, CASE WHEN t1.categoryLevel = 4 THEN t1.categoryName ELSE '' END AS item_category_level4_name FROM md__category_b_o AS t1 LEFT JOIN md__category_b_o AS t2 ON t1.parentCategory = t2.id LEFT JOIN md__category_b_o AS t3 ON t2.parentCategory = t3.id LEFT JOIN md__category_b_o AS t4 ON t3.parentCategory = t4.id ) t8 on t7.category = coalesce(t8.item_category_level4_id,t8.item_category_level3_id,t8.item_category_level2_id,t8.item_category_level1_id) left join  md__brand_b_o t10 on t7.brand = t10.id left join trade__fulfillment_progress_line_b_o t13 on t3.id = t13.tradeOrderLine left join  trade__fulfillment_progress_b_o t12 on t13.fulfillmentProgress = t12.id left join  md__entity_b_o t5 on t12.fulfillmentServiceProvider = t5.id inner join  (select * from md__entity_role_b_o where roleType = 'CARRIER') t4 on t4.entity = t5.id

执行后结果为:

SELECT t12.serviceCode AS logistics_id, t5.entityName AS logistics_name, t12.fulfillmentSite AS delivery_warehouse_name, t7.itemCode AS item_code, t7.itemName AS item_name
	, t10.brandName AS brand_name
	, CONCAT_WS('/', t8.item_category_level1_name, t8.item_category_level2_name, t8.item_category_level3_name, t8.item_category_level4_name) AS categroy_name
	, t7.year AS item_year, t7.season AS item_season
	, substr(t7.launchDate, 1, 10) AS launch_date
	, t6.skuCode AS sku_code
	, coalesce(if(JSON_EXTRACT(t6.attributes, '$[0].attrKey') LIKE '%颜色%', JSON_EXTRACT(t6.attributes, '$[0].attrVal'), if(JSON_EXTRACT(t6.attributes, '$[1].attrKey') LIKE '%颜色%', JSON_EXTRACT(t6.attributes, '$[1].attrVal'), NULL))) AS sku_color
	, coalesce(if(JSON_EXTRACT(t6.attributes, '$[1].attrKey') LIKE '%尺码%', JSON_EXTRACT(t6.attributes, '$[1].attrVal'), if(JSON_EXTRACT(t6.attributes, '$[0].attrKey') LIKE '%尺码%', JSON_EXTRACT(t6.attributes, '$[0].attrVal'), NULL))) AS sku_size
	, t3.tradeQty AS item_cnt, t6.salePrc_value AS sku_price
FROM trade__trade_base_order_line_b_o_all t3
	LEFT JOIN item__sku_b_o_all t6 ON JSON_EXTRACT(t3.sku, '$.id') = t6.id
	LEFT JOIN item__item_b_o_all t7 ON t6.item = t7.id
	LEFT JOIN (
		SELECT CASE 
				WHEN t1.categoryLevel = 1 THEN t1.id
				WHEN t1.categoryLevel = 2 THEN t2.id
				WHEN t1.categoryLevel = 3 THEN t3.id
				WHEN t1.categoryLevel = 4 THEN t4.id
				ELSE ''
			END AS item_category_level1_id
			, CASE 
				WHEN t1.categoryLevel = 1 THEN t1.categoryName
				WHEN t1.categoryLevel = 2 THEN t2.categoryName
				WHEN t1.categoryLevel = 3 THEN t3.categoryName
				WHEN t1.categoryLevel = 4 THEN t4.categoryName
				ELSE ''
			END AS item_category_level1_name
			, CASE 
				WHEN t1.categoryLevel = 2 THEN t1.id
				WHEN t1.categoryLevel = 3 THEN t2.id
				WHEN t1.categoryLevel = 4 THEN t3.id
				ELSE ''
			END AS item_category_level2_id
			, CASE 
				WHEN t1.categoryLevel = 2 THEN t1.categoryName
				WHEN t1.categoryLevel = 3 THEN t2.categoryName
				WHEN t1.categoryLevel = 4 THEN t3.categoryName
				ELSE ''
			END AS item_category_level2_name
			, CASE 
				WHEN t1.categoryLevel = 3 THEN t1.id
				WHEN t1.categoryLevel = 4 THEN t2.id
				ELSE ''
			END AS item_category_level3_id
			, CASE 
				WHEN t1.categoryLevel = 3 THEN t1.categoryName
				WHEN t1.categoryLevel = 4 THEN t2.categoryName
				ELSE ''
			END AS item_category_level3_name
			, CASE 
				WHEN t1.categoryLevel = 4 THEN t1.id
				ELSE ''
			END AS item_category_level4_id
			, CASE 
				WHEN t1.categoryLevel = 4 THEN t1.categoryName
				ELSE ''
			END AS item_category_level4_name
		FROM md__category_b_o_all t1
			LEFT JOIN md__category_b_o_all t2 ON t1.parentCategory = t2.id
			LEFT JOIN md__category_b_o_all t3 ON t2.parentCategory = t3.id
			LEFT JOIN md__category_b_o_all t4 ON t3.parentCategory = t4.id
	) t8
	ON t7.category = coalesce(t8.item_category_level4_id, t8.item_category_level3_id, t8.item_category_level2_id, t8.item_category_level1_id)
	LEFT JOIN md__brand_b_o_all t10 ON t7.brand = t10.id
	LEFT JOIN trade__fulfillment_progress_line_b_o_all t13 ON t3.id = t13.tradeOrderLine
	LEFT JOIN trade__fulfillment_progress_b_o_all t12 ON t13.fulfillmentProgress = t12.id
	LEFT JOIN md__entity_b_o_all t5 ON t12.fulfillmentServiceProvider = t5.id
	INNER JOIN (
		SELECT *
		FROM md__entity_role_b_o_all
		WHERE roleType = 'CARRIER'
	) t4
	ON t4.entity = t5.id

可以看到所有表名都做了转义且不需要修改别名。
如果需要修改SQL语法树其他节点,也与此方法类似。