package cn.slow.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
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.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
/**
* @Intercepts:为Mybatis注解表示当前类为Mybatis拦截器。此拦截器参数为一个数组其参数@Signature注解用于指定拦截哪些Mybatis核心对象下的方法。
* 如下四个方法分别代表:
* update:执行update/insert/delete
* query:执行查询,先在缓存里面查找
* query:执行查询
* queryCursor:执行查询,查询结果放在Cursor里面
*/
@Slf4j
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class, RowBounds.class})
})
public class ExecutorSqlInterceptor implements Interceptor {
/**
* 慢SQL时间阈值
*/
@Value("${mybatis.sql.log.time}")
private BigDecimal logTime;
/**
* 日志级别
*/
@Value("${mybatis.sql.log.logLevel}")
private String logLevel;
/**
* 日志记录开关,是否开启日志记录
*/
@Value("${mybatis.sql.log.switch}")
private Boolean logSwitch;
/**
* 对于时间类型参数,默认时间格式
*/
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
* invocation.getArgs():返回值为数组。
* 该数组中有两个元素,0号元素存放执行当前SQL的MappedStatement对象。1号元素存放的是当前SQL的所有入参(键值对)形如:{key1=value1, key2=value2}
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
if (!logSwitch){ // 是否开启对慢SQL日志记录
return invocation.proceed(); // 直接放行执行SQL
}
// 计时器 - 开始时间
long start = System.currentTimeMillis();
Object result = invocation.proceed();
// 计时器 - 结束时间
long end = System.currentTimeMillis();
// SQL执行耗时
long timing = end - start;
// SQL执行耗时
BigDecimal timingBigDecimal = new BigDecimal(timing);
// 慢SQL时间阈值
BigDecimal maxTime = logTime.multiply(new BigDecimal("1")); // 3000ms
if (timingBigDecimal.compareTo(maxTime) >= 0) { // 当前SQL执行时间大于慢SQL阈值时间(表示此条SQL为慢SQL)
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; // 获取执行当前SQL的MappedStatement对象
Object parameterObject = null;
if (invocation.getArgs().length > 1) {
parameterObject = invocation.getArgs()[1]; // 获取执行当前SQL的参数,形如:{key1=value1, key2=value2}
}
String statementId = mappedStatement.getId(); // 获取Mapper层当前执行SQL所在方法的全限定名形如:cn.slow.mapper.StuMapper.save
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject); // BoundSql对象源码翻译:获取实际SQL字符串,SQL可以有SQL占位符“?”和一个参数映射列表(有序),其中包含每个参数的附加信息(至少是要读取值的输入对象的属性名)。
Configuration configuration = mappedStatement.getConfiguration(); // Mybatis会在启动时读取所有的配置文件加载到内存中,Configuration对象就是承载整个配置的类。
String sql = getSql(boundSql, parameterObject, configuration);
switch (logLevel){ // 以配置文件指定的日志级别输出日志信息
case "debug":
if (log.isDebugEnabled()){
log.debug("执行sql耗时:{} ms - id:{} - Sql:{}", timing, statementId, sql);
}
break;
default:
if (log.isInfoEnabled()){
log.info("执行sql耗时:{} ms - id:{} - Sql:{}", timing, statementId, sql);
}
}
}
return result;
}catch (Exception e){
log.error("拦截sql异常:",e);
}
return invocation.proceed();
}
/**
* plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
*
* 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
* 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 此方法用于将参数与SQL占位符对应起来
*/
private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
String sql = boundSql.getSql().replaceAll("[\\s]+", " "); // 获取SQL
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); // 获取SQL参数映射列表
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 获取到类型处理器。类型处理器TypeHandlerRegistry用于处理javaType与jdbcType之间的类型转换用的处理器,Mybatis针对诸多Java类型与数据库类型进行了匹配处理
if (parameterMappings != null) { // 参数映射列表为空代表当前SQL没有从外部传入的参数。
for (int i = 0; i < parameterMappings.size(); i++) { // 循环参数列表
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) { // 参数映射的类型有IN OUT INOUT,如果是OUT则不用做处理
Object value;
String propertyName = parameterMapping.getProperty(); // 获取映射参数的名称
if (boundSql.hasAdditionalParameter(propertyName)) { // 判断当前SQL中是否有此参数名,有的话根据参数名获取其值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) { // parameterObject用来存储映射关系形如:{key1=value1, key2=value2}。Ma
没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
收起资源包目录
slow-sql.zip (19个子文件)
slow-sql
pom.xml 2KB
src
test
java
main
resources
log4j2.xml 4KB
application.yml 666B
java
cn
slow
mapper
StuMapper.java 293B
controller
StuController.java 439B
App.java 283B
config
ExecutorSqlInterceptor.java 9KB
slow-sql.iml 81B
.idea
workspace.xml 23KB
misc.xml 501B
compiler.xml 777B
encodings.xml 209B
target
classes
cn
slow
mapper
StuMapper.class 387B
controller
StuController.class 1010B
App.class 704B
config
ExecutorSqlInterceptor.class 7KB
log4j2.xml 4KB
application.yml 666B
META-INF
slow-sql.kotlin_module 16B
generated-sources
annotations
共 19 条
- 1
资源评论
钗头风
- 粉丝: 203
- 资源: 10
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功