背景
从事运维或DBA工作的童鞋会非常熟悉在SQL前部增加注释的操作。类似如下的SQL语句:
/* appUk:[testapp];host ip:[192.168.1.111];traceId:[dcb7f7a0cbe72817];spanId[dcb7f7a0cbe72817] */
INSERT INTO test_table ( project_id, tenant, c_project_id, g_ra_type, g_ra_version, g_ra_config, create_by, update_by ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )
这种注释虽然不会影响SQL执行,但是会为运维和DBA工作带来极大的便利如:
- 对慢查询进行优化时,可以通过注释信息快速找到研发团队及研发人员,同时研发人员也可以快速定位到对应业务请求。
- 在出现数据库死锁,阻塞时,可快速定位问题源头。
- 在进行复杂的数据库运维操作时(如数据库迁移),能够与研发团队进行高效合作
- (还有很多,欢迎大家分享自己的经验)
特别是在一些有着规模化研发团队的公司内,这种注释在运维工作中变得极为重要。一些互联网大厂甚至会在SQL规范中明确要求增加该注释。
在笔者所在的团队,虽然团队规模不大,但是面对公司业务不断增长的形势,对运维工作进行规范化是非常必要的。所以笔者开始在公司开发环境下,找寻可行的解决方案。
环境
- springboot 2.6.11
- mybatis-plus:3.5.3.1
- mybatis:3.5.11
业务目标
- 在所有SQL头部增加注释,格式:/* xxxxxxxxxxxxxxxxxxxxxxxx */
- 注释中包含:
- appUk(应用标识)
- host ip (宿主机IP地址)
- 请求追踪信息(brave追踪协议)
技术实现
实现这种技术目标的解决方案有多种。笔者在这个技术方案中,使用了MyBatis的拦截器接口org.apache.ibatis.plugin.Interceptor
。关于拦截器的详细使用方法,可以参看《mybatis:自定义实现拦截器插件Interceptor》这篇文章。
拦截器实现代码如下:
package test.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.MDC;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
/**
* MyBatisPlusSqlAnnotationInterceptor
* <p>
* 用于为MyBatisPlus的SQL语句添加注释,标记语句的执行者
*
* @author John Chen
* @since 2023/5/6
*/
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
@Slf4j
public class MyBatisPlusSqlAnnotationInterceptor implements Interceptor {
private final String appUk;
private String ip = "UNKNOWN";
public MyBatisPlusSqlAnnotationInterceptor(String appUk) {
this.appUk = appUk;
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("获取本机IP失败", e);
}
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
String annotation = String.format("/* **********appUk:[%s];host ip:[%s];traceId:[%s];spanId[%s]********** */",
appUk, ip, MDC.get("traceId"), MDC.get("spanId"));
// 在SQL语句前面加上注释
sql = annotation + sql;
// 用反射修改boundSql的sql属性
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
}
在完成拦截器的主体代码后,需要将它注入到MyBatis的SqlSessionFactory
中。在笔者的项目中,这块代码是通过Java Config的方式实现的。文章来源:https://www.toymoban.com/news/detail-435168.html
public class MySqlConfig {
@Value("${spring.application.name}")
private String appUk;
/**
* 定义一个统一增加注释的拦截器
*/
@Bean
public MyBatisPlusSqlAnnotationInterceptor myBatisPlusSqlAnnotationInterceptor() {
return new MyBatisPlusSqlAnnotationInterceptor(appUk);
}
/**
* 构建SqlSessionFactory
* <p>
* 注意,这里构建后会影响自动配置类{@link com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory(DataSource)}
* 的构建。后期如果增加插件,请务参照对照方法在内部增加注入配置
*/
@Bean
public SqlSessionFactory testIwrsSqlSessionFactory(DataSource dataSource, GlobalConfig globalConfig, MybatisPlusInterceptor paginationInterceptor, MyBatisPlusSqlAnnotationInterceptor myBatisPlusSqlAnnotationInterceptor) throws Exception {
final MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/test/**Mapper.xml")
);
sqlSessionFactoryBean.setTypeAliasesPackage("test.common.entity.*");
sqlSessionFactoryBean.setGlobalConfig(globalConfig);
sqlSessionFactoryBean.setConfiguration(defaultMybatisConfiguration());
//**核心代码看这里**
Interceptor[] plugins = new Interceptor[]{
paginationInterceptor
//自动为SQL增加注释的拦截器
, myBatisPlusSqlAnnotationInterceptor
};
sqlSessionFactoryBean.setPlugins(plugins);
return sqlSessionFactoryBean.getObject();
}
通过如上2步的配置,就完成了统一增加注释的功能文章来源地址https://www.toymoban.com/news/detail-435168.html
Tips
- annotation变量中的注释内容,应当根据公司规定和项目实际情况进行调整。目的是能够将SQL和具体的团队、业务、请求进行快速关联,以方便跨职能,甚至跨部门的技术协作。
- 一些云数据库,可能会通过SQL前部注释的方式完成一些特定功能。如阿里云的PolarDB for AI、PolarDB-X等。这种情况下,增加额外的注释是否会导致具体的功能无法正常运行,需要再实际落地过程中进行验证。
到了这里,关于通过实现MyBatis的Interceptor接口在SQL头部增加统一注释的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!