引言
打印执行的sql语句,有利于我们及时排查错误,而且mybatis也支持sql语句的打印,如果日志采用logback,只需要logback配置里配置一个logger name指向你项目dao包路径即可。类似于下面这种格式。
<logger name="com.x.x.dao.XxxDao" level="DEBUG" />
虽然sql语句可以成功打印出来,但它把一个完整的sql拆分成两部分显示,boundSql和参数,当我们参数很多的时候,想拷贝sql去数据库跑一下就很麻烦。所以说打印一个完整的一复制就能跑的sql还是很重要的。
两种方案
方案1:利用idea的插件
如果你使用的软件是idea的话,方案1就是可行的。可以去plugins下载插件 MyBatis Log Plugin,插件是免费的,其实这种方案也有局限性,就是日志要输出在控制台里才可以。安装完成后可以在Tools中找到。首先看下控制台sql的输出格式。
第一行---->Preparing---->sql语句
第二行---->Paramters---->参数
第三行---->Total
接下来看下MyBatis Log这个插件里的输出格式。
简洁明了,直接输出的就是替换了?的完整可执行sql语句,这种方案虽然很简单并且可以打印出完整sql语句,但是局限性很大,不好进行持久化操作,一般都会将sql执行写入日志中,接下来看下方案二。
方案2:实现一个拦截器来进行sql语句拼接并打印
这种方案比较灵活,可以在代码中获得完整的sql,然后指定日志输出就好了,直接上代码来进行分析。
@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 ExecteSqlLogInterceptor implements Interceptor {
private static Logger daoLogger = LoggerFactory.getLogger("xxx");
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
String sqlId = mappedStatement.getId();
int index = sqlId.indexOf("mapper");
if(index != -1){
sqlId = sqlId.substring(index+7);
}
long start = System.currentTimeMillis();
Object returnValue = invocation.proceed();
long end = System.currentTimeMillis();
long time = end - start;
if(time > 1){
String exexcSql = getSql(configuration, sqlId, time ,boundSql);
//System.out.println(exexcSql);
daoLogger.info(exexcSql);
}
return returnValue;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
private static String getRuntimeExeSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && 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;
}
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} 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;
}
private String getSql(Configuration configuration, String sqlId, long time, BoundSql boundSql){
String sql = getRuntimeExeSql(configuration, boundSql);
StringBuilder result = new StringBuilder();
result.append(" ["+sqlId+"] ");
result.append(" ==> ");
result.append(sql);
result.append(" ==> ");
result.append(time);
result.append("ms");
return result.toString();
}
首先先看下@Intercepts这里定义成了一个拦截器,拦截了query,update操作,然后下面的daoLogger是日志输出的配置,xxx代表你logback里配置的loggerName,可以下面配置appender进行细化配置,日志的输出配置不多述了。主要看下我们能从invocation这个对象得到哪些属性。
主要的参数是从args里拿出来,而且带?的sql语句可以从rootSqlNode获得,id是方法的全限定名。
下面的MapperMethod$ParamMap这里就是参数,最后就是分页参数。由三部分组成。
BoundSql boundSql = mappedStatement.getBoundSql(parameter);这里得到带?的sql语句,主要替换操作是在getRuntimeExeSql这个函数里实现,把每一个?对应的参数拿出来,看一眼代码就懂作用了,不多解释了。得到之后logger.info打印到日志就ok了。其实可以直接打个断点进去,然后看看对应的参数,调用过程。然后记得要在mybatis里面加入这个拦截器。
相对于第一种方案来说,第二种麻烦了很多,但第二种灵活,可以把sql输出到日志并且输出一些你想要的信息,比如运行时间之类。
To live is to function --xxy专用小尾巴。