环境
Mybatis、Druid(DataSource)
原因
业务需要及想进一步了解Mybatis知识。
下面就只写怎么做了,原理就一步一步去debug吧,自己动手能了解更多。
输出SQL预编译语句
共有两个方式能获取预编译后的语句:Mybatis拦截器、druid过滤器
Mybatis拦截器
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlOutputInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// invocation.getArgs() 的值是 @Signature 注解里面的 args 列表
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object[] parameterObject = invocation.getArgs();
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
Configuration configuration = mappedStatement.getConfiguration();
// 获取未处理的 SQL 字符串
String sql = boundSql.getSql();
// 若想看真正执行的SQL语句,详情请看 DefaultParameterHandler.java 的 setParameters(PreparedStatement ps)
// 输出编译后的语句,带 “?” 的不可执行语句
log.info(sql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
注意的两个点:
1、BoundSql 存储了预编译后的 sql,是带 “?” 的不可直接执行的sql。
2、可执行的sql语句其实在mybatis发送往数据库前就已经拼好了,但数据是以 byte[] 方式存储的,且 Encoding 是 “utf-8” 存储的。原理上,可以先获取这个 byte[] 以及 Encoding。然后 new String(byte[], Encoding) 的方式直接获取可执行的 sql。
Druid(DataSource) 过滤器
@Slf4j
public class SqlOutputFilter extends FilterEventAdapter {
@Override
public void init(DataSourceProxy dataSource) {
super.init(dataSource);
log.info("初始化SqlOutputFilter。");
}
@Override
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
super.statementExecuteAfter(statement, sql, result);
log.info("自定义过滤器,在执行操作后执行该方法,输出SQL={}", sql);
int parametersSize = statement.getParametersSize();
String formattedSql = sql;
if (parametersSize > 0) {
List<Object> parameters = new ArrayList<>(parametersSize);
for (int i = 0; i < parametersSize; i++) {
JdbcParameter parameter = statement.getParameter(i);
parameters.add(parameter != null ? parameter.getValue() : null);
}
String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
formattedSql = SQLUtils.format(sql, dbType, parameters);
}
log.info("执行SQL语句,SQL={}", formattedSql);
}
}
这个是参考了 Druid 的 LogFilter ,直接调用SQLUtils.format(String sql, String dbType, List parameters) 来获取可执行的 sql 语句。
执行顺序
拦截器跟过滤器都是递归方式执行:
正向:Mybatis 拦截器 -> Druid 过滤器
反向:Druid 过滤器 -> Mybatis 拦截器