我们的目的是通过在mapper方法上打注解@SqlLog来判定sql需不需要打印
首先定义一个注解, 可以打在方法上, 也可以打在类上, 来判定是否打印, 方法上具有更高优先级
/**
* @author npj
* @date 2019-08-02
* 打印sql
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlLog {
/**
* 是否打印sql
*/
boolean print() default true;
}
通过mybatis拦截器获取sql, 再获取mapper方法上是否有对应的注解来判定是否打印sql
import com.google.common.base.Stopwatch;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Sql打印
* @author npj
*/
@Slf4j(topic = "sqlPrint")
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
})
public class SqlPrint implements Interceptor {
private boolean defaultPrint = true;
/**
* 缓存
* key : statement id,
* value : 是否打印
*/
private Map<String, Boolean> mappedStatementPrintSqlMap = Maps.newConcurrentMap();
public SqlPrint() {
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = invocation.proceed();
long executionTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
try {
logSql(invocation, executionTime);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info("打印sql异常", e);
}
// 新增功能不影响原有功能
}
return result;
}
/**
* 打印sql
*/
private void logSql(Invocation invocation, long executionTime) throws ClassNotFoundException, NoSuchMethodException {
StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStmtHandler = SystemMetaObject.forObject(stmtHandler);
MappedStatement mappedStatement = (MappedStatement) metaStmtHandler.getValue("delegate.mappedStatement");
// 获取方法上注解
String fullMapperMethod = mappedStatement.getId();
// 没有注解, 默认不打sql, 直接返回
// 有注解, 指定不打印, 直接返回
if (mappedStatementPrintSqlMap.get(fullMapperMethod) == null) {
boolean logSql = needLogSql(fullMapperMethod);
mappedStatementPrintSqlMap.put(fullMapperMethod, logSql);
}
// 方法是否要打印sql
if (!mappedStatementPrintSqlMap.get(fullMapperMethod)) {
log.info("方法:{},执行时间{}ms", getMapperMethodName(fullMapperMethod), executionTime);
return;
}
// 获取参数类型
BoundSql boundSql = (BoundSql) metaStmtHandler.getValue("delegate.boundSql");
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 解析?对应的参数
List<String> parameters = parseParameter(metaStmtHandler, mappedStatement, boundSql, parameterMappings);
String sql = boundSql.getSql();
for (String value : parameters) {
sql = sql.replaceFirst("\\?", value);
}
log.info("方法:{},执行时间{}ms {}", getMapperMethodName(fullMapperMethod), executionTime, beautifySql(sql));
}
/**
* 解析参数
*/
private List<String> parseParameter(MetaObject metaStmtHandler, MappedStatement mappedStatement, BoundSql boundSql, List<ParameterMapping> parameterMappings) {
List<String> parameters = new ArrayList<>();
if (parameterMappings != null) {
Object parameterObject = metaStmtHandler.getValue("delegate.boundSql.parameterObject");
Configuration configuration = mappedStatement.getConfiguration();
MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 参数值
Object value;
String propertyName = parameterMapping.getProperty();
// 获取参数名称
if (boundSql.hasAdditionalParameter(propertyName)) {
// 获取参数值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 如果是单个值则直接赋值
value = parameterObject;
} else {
value = metaObject == null ? null : metaObject.getValue(propertyName);
}
if (value instanceof Number) {
parameters.add(String.valueOf(value));
} else {
StringBuilder builder = new StringBuilder();
builder.append("'");
if (value instanceof Date) {
builder.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) value));
} else if (value instanceof String) {
builder.append(value);
}
builder.append("'");
parameters.add(builder.toString());
}
}
}
}
return parameters;
}
/**
* 获取方法或者类上的sqlLog注解, 判断是否打sql
*/
private boolean needLogSql(String fullMapperMethod) throws ClassNotFoundException, NoSuchMethodException {
Class<?> mapperClass = getMapperClass(fullMapperMethod);
// 目标方法
String targetMethodName = getMapperMethodName(fullMapperMethod);
// 拿不到参数类型列表?, 这里循环下, 正好mybatis不能重载
return ! Arrays.stream(mapperClass.getMethods())
.filter(method -> method.getName().equals(targetMethodName))
.findFirst()
.map(method -> {
// 获取方法上注解
SqlLog sqlLog = method.getAnnotation(SqlLog.class);
if (sqlLog == null) {
sqlLog = mapperClass.getAnnotation(SqlLog.class);
}
return (sqlLog == null && !defaultPrint) || (sqlLog != null && !sqlLog.print());
}).orElse(false);
}
private String getMapperMethodName(String fullMapperMethod) {
return fullMapperMethod.substring(fullMapperMethod.lastIndexOf(".") + 1);
}
private Class<?> getMapperClass(String fullMapperMethod) throws ClassNotFoundException {
String mapperClassName = fullMapperMethod.substring(0, fullMapperMethod.lastIndexOf("."));
return Class.forName(mapperClassName);
}
private String beautifySql(String sql) {
return sql.replaceAll("[\\s\n ]+", " ");
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
public boolean isDefaultPrint() {
return defaultPrint;
}
public void setDefaultPrint(boolean defaultPrint) {
log.info("defaultPrintSql = {}", defaultPrint);
this.defaultPrint = defaultPrint;
}
}
与spring boot集成, 定义一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SqlConfig.class)
@Documented
public @interface EnableSqlPrint {
/**
* 是否默认打sql, 优先级最低,
* 可以通过和SqlLog注解组合打印sql
* sqlLog具有最高优先级
*/
boolean defaultPrint() default true;
}
解析注解, 把SqlPrint组件手动注入到bean定义, 并设置属性
public class SqlConfig implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes sqlPrint = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableSqlPrint.class.getName()));
if (sqlPrint == null) {
return;
}
boolean defaultPrintSql = sqlPrint.getBoolean("defaultPrint");
BeanDefinitionBuilder sqlPrintBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(SqlPrint.class);
sqlPrintBeanDefinition.addPropertyValue("defaultPrint", defaultPrintSql);
registry.registerBeanDefinition(SqlPrint.class.getName(), sqlPrintBeanDefinition.getBeanDefinition());
}
}