一、前戏
故事背景
公司引入一套阿里云的大数据系统,汇集公司的多张表。
正式环境使用的是大数据中间件2.0,正式环境是3.0,3.0环境查询需要 “数据库名.表名”,2.0环境查询需要“表名”。
需要实现一个根据环境不同,修改表名的功能。
常见的技术方案选型
- 方式1:最直观的,维护两套mybatis的sql,根据环境不同加载不同sql文件,维护成本大
- 方式2:提取公共xml文件,将表名抽成公共变量,进行维护,维护成本大,sql编写不直观。
https://blog.csdn.net/wohaqiyi/article/details/84993681 - 方式3:(推荐)使用mybatis的拦截器,根据环境变量的不同,替换表名
https://mybatis.org/mybatis-3/zh/configuration.html#plugins
二、正文
配置文件bootstrap.yml
# 正式站与测试站表明对应配置文件
# 正式站表名: 测试站表名
dataworks:
tableName:
prodTableName: testDataBaseName.testTableName
解析配置信息
/**
* @Auther: fatsnake
* @Description":
* @Date:2022/9/14 13:18
* Copyright (c) 2022, zaodao All Rights Reserved.
*/
@Configuration
@ConfigurationProperties(prefix = "dataworks")
public class DataBaseTableNameConfig {
/**
* 表名集合
*/
private Map<String, String> tableName;
/**
* 构造函数
*/
DataBaseTableNameConfig() {
}
public Map<String, String> getTableName() {
return tableName;
}
public void setTableName(Map<String, String> tableName) {
this.tableName = tableName;
}
@Override
public String toString() {
return "DataBaseTableNameConfig{"
+ "tableName=" + tableName
+ '}';
}
}
mybatis插件按环境条件生效
@Profile({"dev", "test"})
public class FrameMyBatisPluginConfig {
/**
*
* @param sqlSessionFactory sqlSessionFactory
* @return String
*/
@Bean
public String sqlTableNameHandleInterceptor(SqlSessionFactory sqlSessionFactory) {
//实例化插件
SQLTableNameHandleInterceptor sqlTableNameHandleInterceptor = new SQLTableNameHandleInterceptor();
// 为后续留好扩展
// //创建属性值
// Properties properties = new Properties();
// properties.setProperty("prop1","value1");
// //将属性值设置到插件中
// sqlTableNameHandleInterceptor.setProperties(properties);
//将插件添加到SqlSessionFactory工厂
sqlSessionFactory.getConfiguration().addInterceptor(sqlTableNameHandleInterceptor);
return "interceptor";
}
}
使用mybatis的StatementHandler插件,在sql执行前进行拦截
此处使用了druid的工具类,解析表名,替换表名后重写表名
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.util.JdbcConstants;
/**
* @Auther: fatsnake
* @Description":
* @Date:2022/9/14 12:47
* Copyright (c) 2022, zaodao All Rights Reserved.
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class SQLTableNameHandleInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
DataBaseTableNameConfig dataBaseTableNameConfig
= (DataBaseTableNameConfig)SpringContextUtils.getBean("dataBaseTableNameConfig");
Map<String, String> tableNameMap = dataBaseTableNameConfig.getTableName();
if (!CollectionUtils.isEmpty(tableNameMap)) {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, new DefaultObjectFactory(),
new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
String sql = boundSql.getSql();
List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
MySqlExportTableAliasVisitor visitor = new MySqlExportTableAliasVisitor();
for (SQLStatement stmt : stmtList) {
stmt.accept(visitor);
}
String handleSQL = SQLUtils.toSQLString(stmtList, JdbcConstants.MYSQL);
metaStatementHandler.setValue("delegate.boundSql.sql", handleSQL);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
实际替换操作
/**
* @Auther: fatsnake
* @Description":
* @Date:2022/9/14 12:46
* Copyright (c) 2022, zaodao All Rights Reserved.
*/
public class MySqlExportTableAliasVisitor extends MySqlASTVisitorAdapter {
@Override
public boolean visit(SQLExprTableSource x) {
DataBaseTableNameConfig dataBaseTableNameConfig
= (DataBaseTableNameConfig) SpringContextUtils.getBean("dataBaseTableNameConfig");
Map<String, String> tableNameMap = dataBaseTableNameConfig.getTableName();
if (tableNameMap.containsKey(x.getExpr().toString())) {
x.setExpr(tableNameMap.get(x.getExpr().toString()));
}
return true;
}
}
三、尾声
本来不了解这个mybatis的插件机制的,当时接到这个需求时,只是想着如何无感知的让业务开发人员使用,并且项目便于维护sql,最好不要维护两套sql,经过百度老师的指导,整合形成了这个结束解决方案。
暴露出,对常用的中间件 还是不熟悉啊,源代码读的还是少……
网友评论