MyBatis - 拦截器之打印最终完整可直接执行带参数 SQL
Posted 放羊的牧码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis - 拦截器之打印最终完整可直接执行带参数 SQL相关的知识,希望对你有一定的参考价值。
故事背景
大家还记得那些年的 P6spy,这个也是打印完整可直接执行 SQL 的利器,不过总感觉,我个人使用下来还是觉得太重,配置驱动,引入 Jar,啥啥啥的,今天无聊下给大家整了一个类似这个功能的,可以看看~
拦截器(核心)
import com.chinadaas.platform.dsp.pipeline.annotation.SqlFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
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.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.regex.Matcher;
/**
* @author Lux Sun
* @date 2022/6/27
*/
@Slf4j
@Intercepts(
@Signature(type = org.apache.ibatis.executor.Executor.class, method = "update", args =
MappedStatement.class, Object.class
)
)
public class SqlInterceptor implements Interceptor
@Override
public Object intercept(Invocation invocation) throws Throwable
try
// 获取xml中的一个select/update/insert/delete节点,是一条SQL语句
MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
Object parameter = null;
// 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式
if (invocation.getArgs().length > 1)
parameter = invocation.getArgs()[1];
System.out.println("parameter = " + parameter);
// 获取到节点的id, 即sql语句的id
String sqlId = mappedStatement.getId();
System.out.println("sqlId = " + sqlId);
// BoundSql就是封装myBatis最终产生的sql类
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
// 获取节点的配置
Configuration configuration = mappedStatement.getConfiguration();
// 获取到最终的sql语句
String sql = getSql(configuration, boundSql, sqlId);
System.out.println("sql = " + sql);
catch (Exception e)
e.printStackTrace();
// 执行完上面的任务后,不改变原有的sql执行过程
return invocation.proceed();
// 封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id + sql语句
private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId)
String sql = showSql(configuration, boundSql);
StringBuilder str = new StringBuilder(100);
str.append(sqlId);
str.append(":");
str.append(sql);
return str.toString();
// 如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理
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 static String showSql(Configuration configuration, BoundSql boundSql)
// 获取参数
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// sql语句中多个空格都用一个空格代替
String sql = boundSql.getSql().replaceAll("[\\\\s]+", " ");
if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null)
// 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 如果根据parameterObject.getClass()可以找到对应的类型,则替换
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass()))
sql = sql.replaceFirst("\\\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
else
// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
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("\\\\?",
Matcher.quoteReplacement(getParameterValue(obj)));
else if (boundSql.hasAdditionalParameter(propertyName))
// 该分支是动态sql
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\\\?",
Matcher.quoteReplacement(getParameterValue(obj)));
else
// 打印出缺失,提醒该参数缺失并防止错位
sql = sql.replaceFirst("\\\\?", "缺失");
return sql;
@Override
public Object plugin(Object target)
log.info("SqlInterceptor plugin");
return Interceptor.super.plugin(target);
@Override
public void setProperties(Properties properties)
log.info("SqlInterceptor setProperties");
Interceptor.super.setProperties(properties);
Ps:这边拦截只是针对 Update 操作,这个在上面自己配上 @Signature 即可,这个很简单就不再描述,对应填写的参数看下都会的!
注册拦截器
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception
// 用 mybatis 的这里会有点区别, mybatis 用的是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
sqlSessionFactoryBean.setMapperLocations((new PathMatchingResourcePatternResolver()).getResources("classpath:mapper/*.xml"));
// 注册 SQL 拦截器
sqlSessionFactoryBean.setConfiguration(new MybatisConfiguration());
sqlSessionFactoryBean.getConfiguration().addInterceptor(new SqlInterceptor());
return sqlSessionFactoryBean.getObject();
以上是关于MyBatis - 拦截器之打印最终完整可直接执行带参数 SQL的主要内容,如果未能解决你的问题,请参考以下文章