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);
 }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章