MyBatis打印完整SQL
前言
在开发过程中,往往需要把执行的Sql给打印出来,通过查看SQL日志可以帮助开发者了解应用程序实际执行的SQL语句,这对于调试数据库相关的错误或查询问题非常有帮助。在此记录一下MyBatis开启SQL日志的相关配置。
Spring+MyBatis项目
在项目中的mybatis-config.xml文件中增加如下配置:
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>Spring Boot+MyBatis项目
在项目中的Application.properties文件中添加如下配置:
mybatis.configuration.log-impl= org.apache.ibatis.logging.stdout.StdOutImpl或在项目中的Application.yml文件中添加如下配置:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImplSpring Boot+MyBatisPlus项目
在项目中的Application.properties文件中添加如下配置:
mybatis-plus.configuration.log-impl= org.apache.ibatis.logging.stdout.StdOutImpl或在项目中的Application.yml文件中添加如下配置:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl控制台打印效果
此处使用一个查询地址的sql作为示例:
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fe9a2fd] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2f867381] will not be managed by Spring
==> Preparing: SELECT id,name,province_id FROM city WHERE (province_id = ?) ORDER BY id ASC
==> Parameters: 44(Integer)
<== Columns: id, name, province_id
<== Row: 4401, 广州市, 44
<== Row: 4402, 韶关市, 44
<== Row: 4403, 深圳市, 44
<== Row: 4404, 珠海市, 44
<== Row: 4405, 汕头市, 44
<== Row: 4406, 佛山市, 44
<== Row: 4407, 江门市, 44
<== Row: 4408, 湛江市, 44
<== Row: 4409, 茂名市, 44
<== Row: 4412, 肇庆市, 44
<== Row: 4413, 惠州市, 44
<== Row: 4414, 梅州市, 44
<== Row: 4415, 汕尾市, 44
<== Row: 4416, 河源市, 44
<== Row: 4417, 阳江市, 44
<== Row: 4418, 清远市, 44
<== Row: 4419, 东莞市, 44
<== Row: 4420, 中山市, 44
<== Row: 4451, 潮州市, 44
<== Row: 4452, 揭阳市, 44
<== Row: 4453, 云浮市, 44
<== Total: 21此时,执行的SQL已经打印到控制台了。但是有的小伙伴就要说了:这个SQL里面占位符(?)和参数还是分开的,复制到可视化工具还是不能直接执行好麻烦哦。
确实是的,相信很多小伙伴都遇到过这种情况,一条关联查询的SQL超长,参数也有很多个,这个时候如果SQL有错误,一时间还真看不出来问题到底出现在哪里。
自定义拦截器打印完整SQL
在MyBtis中,有个拦截器接口org.apache.ibatis.plugin.Interceptor,我们只需要实现此接口,就可以实现我们需要的功能。
实现后需要把拦截器给注册到对应的SqlSessionFactory中方可生效
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})
})
public class PrintSqlInterceptor implements Interceptor {
private final static Logger log = LoggerFactory.getLogger(PrintSqlInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取SQL映射信息对象
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
String sqlId = mappedStatement.getId();
// 获取绑定的SQL对象,包含具体的SQL语句、参数等
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
// 获取配置对象
Configuration configuration = mappedStatement.getConfiguration();
long start = System.currentTimeMillis();
Object returnValue = invocation.proceed();
long time = System.currentTimeMillis() - start;
String sql = this.parseCompleteSql(configuration, boundSql);
log.info("{}SqlLog ==> statement={}, time={}(ms){}FullSql ==> {}", StringPool.NEWLINE, sqlId, time, StringPool.NEWLINE, sql);
return returnValue;
}
public String parseCompleteSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
// 获取参数映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//替换空格、换行、tab缩进等
String sql = boundSql.getSql().replaceAll("\\s+", " ");
if (!parameterMappings.isEmpty() && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
/**
* 解析参数值用于拼接sql
*
* @param obj 参数
* @return obj.toString()
*/
private String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value.replace("$", "\\$");
}
}拦截器打印效果
2024-09-08 10:48:44.804 INFO 38400 --- [io-12580-exec-3] c.z.config.mybatis.PrintSqlInterceptor :
SqlLog ==> statement=cn.zhengzl.mapper.CityMapper.selectList, time=11(ms)
FullSql ==> SELECT id,name,province_id FROM city WHERE (province_id = 44) ORDER BY id ASC
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3fe9a2fd]这样我们就得到了一个可以直接复制到可视化工具中直接执行的sql,可以大大节省我们自己替换参数的时间。
备注
mybatis-config.xml文件中的value的取值范围为:SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING 或实现了org.apache.ibatis.logging.Log的全限定类名。