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);
}
}