MyBatis @Intercepts 实现打印 SQL 语句

1. @Intercepts 简介

MyBatis@Intercepts 注解用于表明当前的对象是一个拦截器,其配置值是一个@Signature数组,而@Signature 则用于声明要拦截的接口、方法以及对应的参数列表。所谓拦截器的作用就是可以拦截某些方法的调用,和 Spring 中的 AOP 是完全一致的。
MyBatis 拦截器设计的初衷是为用户提供一个实现自定义逻辑的解决方法,而不必去改动 MyBatis 自身的逻辑。比如如果认为几种实现Executor接口的子类的query方法都不能满足要求,就可以建立一个拦截器用于拦截 Executor接口的query方法,实现自己的 query方法逻辑

2. 使用 @Intercepts 实现打印 SQL 语句

2.1 实现拦截器

  1. 拦截器的核心逻辑实现类 LogSqlHelper如下,其主要逻辑为取出 MappedStatement 封装的 SQL 语句,将参数格式化后填入 SQL 语句中,再根据 slowSqlThreshold 参数配置判断其耗时是否超过阈值,从而决定是否需要打印 SQL 语句

    public class LogSqlHelper {
    
     private static final Logger log = LoggerFactory.getLogger(LogSqlHelper.class);
    
     private static final String SELECT = "select";
    
     private static final String FROM = "from";
    
     private static final String SIMPLE_SELECT = "select * ";
    
     private static final int MAX_SQL_LENGTH = 120;
    
     private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
    
     public LogSqlHelper() {
     }
    
     public static Object intercept(Invocation invocation, int slowSqlThreshold, boolean optimizeSql) throws Throwable {
         long startTime = System.currentTimeMillis();
         Object returnValue = invocation.proceed();
         long cost = System.currentTimeMillis() - startTime;
         if (cost >= (long) slowSqlThreshold) {
             log.info("cost = {} ms, affected rows = {}, SQL: {}",
                     cost, formatResult(returnValue), formatSql(invocation, optimizeSql));
         }
         return returnValue;
     }
    
     private static Object formatResult(Object obj) {
         if (obj == null) {
             return "NULL";
         } else if (obj instanceof List) {
             return ((List) obj).size();
         } else if (!(obj instanceof Number) && !(obj instanceof Boolean) && !(obj instanceof Date)
                 && !(obj instanceof String)) {
             return obj instanceof Map ? ((Map) obj).size() : 1;
         } else {
             return obj;
         }
     }
    
     private static String formatSql(Invocation invocation, boolean isOptimizeSql) {
         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();
         Object parameterObject = boundSql.getParameterObject();
         List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
         String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
         String formatSql = sql.toLowerCase();
         if (isOptimizeSql && formatSql.startsWith(SELECT) && formatSql.length() > MAX_SQL_LENGTH) {
             sql = SIMPLE_SELECT + sql.substring(formatSql.indexOf(FROM));
         }
    
         if (parameterMappings.size() > 0 && parameterObject != null) {
             TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
             if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                 sql = sql.replaceFirst("\\?", formatParameterValue(parameterObject));
             } else {
                 MetaObject metaObject = configuration.newMetaObject(parameterObject);
                 for (ParameterMapping parameterMapping : parameterMappings) {
                     String propertyName = parameterMapping.getProperty();
                     Object obj;
                     if (metaObject.hasGetter(propertyName)) {
                         obj = metaObject.getValue(propertyName);
                         sql = sql.replaceFirst("\\?", formatParameterValue(obj));
                     } else if (boundSql.hasAdditionalParameter(propertyName)) {
                         obj = boundSql.getAdditionalParameter(propertyName);
                         sql = sql.replaceFirst("\\?", formatParameterValue(obj));
                     }
                 }
             }
         }
         return sql;
     }
    
     private static String formatParameterValue(Object obj) {
         if (obj == null) {
             return "NULL";
         } else {
             String value = obj.toString();
             if (obj instanceof Date) {
                 DateFormat dateFormat = new SimpleDateFormat(PATTERN);
                 value = dateFormat.format((Date) obj);
             }
             if (!(obj instanceof Number) && !(obj instanceof Boolean)) {
                 value = "'" + value + "'";
             }
             return value;
         }
     }
    }
    
  2. 通过 @Intercepts 注解声明 LogQueryAndUpdateSqlHandler 为拦截器,并使用@Signature配置拦截器拦截的目标接口,目标方法,目标方法的入参

    @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 LogQueryAndUpdateSqlHandler implements Interceptor {
     private int slowSqlThreshold;
     private boolean isOptimizeSql;
    
     public LogQueryAndUpdateSqlHandler() {
     }
    
     public LogQueryAndUpdateSqlHandler(int slowSqlThreshold) {
         this.slowSqlThreshold = slowSqlThreshold;
     }
    
     public LogQueryAndUpdateSqlHandler(boolean isOptimizeSql) {
         this.isOptimizeSql = isOptimizeSql;
     }
    
     public LogQueryAndUpdateSqlHandler(int slowSqlThreshold, boolean isOptimizeSql) {
         this.slowSqlThreshold = slowSqlThreshold;
         this.isOptimizeSql = isOptimizeSql;
     }
    
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
         return LogSqlHelper.intercept(invocation, this.slowSqlThreshold, this.isOptimizeSql);
     }
    
     @Override
     public Object plugin(Object target) {
         return target instanceof Executor ? Plugin.wrap(target, this) : target;
     }
    
     @Override
     public void setProperties(Properties properties) {
     }
    }
    

2.2 将拦截器注入 Spring

通过自动配置类 LogSqlAutoConfiguration将拦截器注入到 Spring 中,使拦截器能够真正生效

@ConditionalOnClass({SqlSessionFactory.class})
@Configuration
public class LogSqlAutoConfiguration {
 public LogSqlAutoConfiguration() {
 }

 @Bean
 @ConditionalOnProperty(name = {"log.printQueryAndUpdateSql"}, havingValue = "true", matchIfMissing = true)
 public LogQueryAndUpdateSqlHandler getLogSqlHandler() {
     return new LogQueryAndUpdateSqlHandler(true);
 }
}
发布了107 篇原创文章 · 获赞 99 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_45505313/article/details/104685417