1. @Intercepts 簡介
在MyBatis
中@Intercepts
註解用於表明當前的對象是一個攔截器,其配置值是一個@Signature
數組,而@Signature
則用於聲明要攔截的接口、方法以及對應的參數列表。所謂攔截器
的作用就是可以攔截某些方法的調用,和 Spring
中的 AOP
是完全一致的。
MyBatis
攔截器設計的初衷是爲用戶提供一個實現自定義邏輯的解決方法,而不必去改動 MyBatis
自身的邏輯。比如如果認爲幾種實現Executor
接口的子類的query
方法都不能滿足要求,就可以建立一個攔截器用於攔截 Executor
接口的query
方法,實現自己的 query
方法邏輯
2. 使用 @Intercepts 實現打印 SQL 語句
2.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; } } }
-
通過
@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);
}
}