Spring Boot 整合 MyBatis 项目中,控制台或日志文件中记录的 SQL 中的动态参数通常是不能自动填充的。排查问题时如果想要执行 sql ,还得手动把日志文件中记录的 SQL 中的 ? 替换为相应的参数。
为了使控制台或日志文件中记录的 SQL 复制到数据库图形化工具中时可以直接执行,我们开发了一个 MyBatis 插件。
拦截器代码实现
SqlPrintInterceptor.java —— 自定义 Mybatis 拦截器
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlPrintInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
List<Object> args = Lists.newArrayList(invocation.getArgs());
MappedStatement mappedStatement = (MappedStatement) args.get(0);
Object param = args.size() > 1 ? args.get(1) : null;
// 开始时间
long beginTimeMillis = System.currentTimeMillis();
// SQL执行时间
long costTimeMillis;
// SQL执行结果
Object result;
try {
result = invocation.proceed();
} finally {
costTimeMillis = System.currentTimeMillis() - beginTimeMillis;
}
BoundSql boundSql = mappedStatement.getBoundSql(param);
// SQL,最初的SQL中含有?,我们会将SQL中的?替换成对应的参数(替换掉空格、 制表符 、换行或回车符)
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
// Mapper参数对象
Object parameterObject = boundSql.getParameterObject();
// Mapper参数映射信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandler Registry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
String parameterValue = getParameterValue(obj);
sql = sql.replaceFirst("\\?", parameterValue);
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
String parameterValue = getParameterValue(obj);
sql = sql.replaceFirst("\\?", parameterValue);
}
}
}
}
log.info("sql: {}", sql);
log.info("cost time millis: {}", costTimeMillis);
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
private static String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = new StringBuffer ("'").append(obj).append("'").toString();
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale .CHINA);
value = new StringBuffer("'").append(formatter.format(new Date())).append("'").toString();
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value.replace("$", "\\$");
}
}
拦截器配置
在 MyBatis 的配置文件里配置自定义拦截器:
<plugins>
<plugin interceptor="com.jasmine.plugin.SqlPrintInterceptor" />
</plugins>