在Spring Boot中,MyBatis插件机制通过拦截器(Interceptor)来实现。拦截器允许开发人员在执行SQL语句的各个阶段(如SQL语句创建、参数处理、结果映射等)插入自定义逻辑。MyBatis的拦截器主要用于增强MyBatis的功能,如日志记录、性能监控、实现分页、数据权限控制、SQL日志、动态SQL生成等,在SQL执行之前后,提供了四个拦截点,在方法执行前后进行处理,支持不同场景的功能扩展。
类 | 作用 |
---|---|
Executor | 用于执行增/删/改/查等数据库操作。 |
ParameterHandler | 用于处理 SQL 参数。 |
ResultSetHandler | 用于处理结果集。 |
StatementHandler | 用于处理 SQL 语句的创建和参数化。 |
@Signature参数说明
参数 | 参数说明 |
---|---|
type | 就是指定拦截器类型(Executor, ParameterHandler, StatementHandler, ResultSetHandler)。 |
method | 是拦截器类型中的方法,不是自己写的方法(update, query, prepare …)。 |
args | 是method中方法的入参。 |
Executor接口中常见的方法
方法 | 实现原理 |
---|---|
update | 执行插入、更新和删除操作。 |
query | 执行查询操作,返回结果集。 |
flushStatements | 刷新批量执行的 SQL 语句。 |
commit | 提交事务。 |
rollback | 回滚事务。 |
getTransaction | 获取当前事务对象。 |
close | 关闭执行器。 |
isClosed | 检查执行器是否已关闭。 |
clearLocalCache | 清空本地缓存。 |
Mybatis拦截器的实现原理
方法 | 实现原理 |
---|---|
注册拦截器 | 拦截器需要在 MyBatis 配置文件中注册,通常是在 mybatis-config.xml 中配置,也可以通过 Java 配置类进行注册。 |
实现 Interceptor 接口 | 自定义拦截器需要实现 MyBatis 的 Interceptor 接口,并重写 intercept 方法。这个方法会接收一个 Invocation 对象,表示被拦截的方法调用。 |
调用链 | MyBatis 在执行 SQL 操作时,会按照拦截器的配置顺序,依次调用每个拦截器的 intercept 方法。拦截器可以选择在方法执行前后进行处理,或者直接修改方法参数和返回值。 |
@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})
})
public class MyInterceptor implements Interceptor {
...
}
1 实现代码
import java.text.DateFormat;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.collection.CollUtil;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Matcher;
import java.sql.Connection;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
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.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
@Slf4j
@Component
@ConditionalOnProperty(prefix = "mybatis-plus", name = "customer-log", havingValue = "true")
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisLogger implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
MetaObject object = MetaObject.forObject(handler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
MappedStatement statement = (MappedStatement) object.getValue("delegate.mappedStatement");
log.info("SQL执行类: {}", statement.getId());
log.info("SQL执行类型: {}", statement.getSqlCommandType().toString());
BoundSql bound = handler.getBoundSql();
Configuration configuration = statement.getConfiguration();
String sql = getFullSql(configuration, bound);
log.info("SQL语句: {}", sql);
long start = System.currentTimeMillis();
Object value = invocation.proceed();
long end = System.currentTimeMillis();
log.info("SQL耗时: {}", (end - start));
return value;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可以通过MyBatis配置文件或注解传递属性
}
public String getFullSql(Configuration conf, BoundSql bound) {
Object object = bound.getParameterObject();
List<ParameterMapping> list = bound.getParameterMappings();
String sql = bound.getSql().replaceAll("[\\s]+", " ").toLowerCase(Locale.ROOT);
if (CollUtil.isNotEmpty(list) && object != null) {
TypeHandlerRegistry type = conf.getTypeHandlerRegistry();
if (type.hasTypeHandler(object.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParaValue(object)));
} else {
MetaObject meta = conf.newMetaObject(object);
for (ParameterMapping parameterMapping : list) {
String name = parameterMapping.getProperty();
if (meta.hasGetter(name)) {
Object obj = meta.getValue(name);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParaValue(obj)));
} else if (bound.hasAdditionalParameter(name)) {
Object obj = bound.getAdditionalParameter(name);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParaValue(obj)));
} else {
sql = sql.replaceFirst("\\?", "缺失");
}
}
}
}
return sql;
}
private String getParaValue(Object obj) {
if (obj instanceof String) {
return "'" + obj + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
return "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
return obj.toString();
} else {
return "";
}
}
}
}
2 测试结果
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@264df4aa]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d91483c] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@44d84b5a] will not be managed by Spring
2024-07-05 16:32:27.660 INFO 3264 --- [nio-8221-exec-1] com.xu.hander.MybatisLogger : SQL执行类: com.xu.view.mapper.StudentMapper.selectOne
2024-07-05 16:32:27.660 INFO 3264 --- [nio-8221-exec-1] com.xu.hander.MybatisLogger : SQL执行类型: SELECT
2024-07-05 16:32:27.660 INFO 3264 --- [nio-8221-exec-1] com.xu.hander.MybatisLogger : SQL语句: select id,create_by,create_time,create_hour,create_date,update_by,update_time,tenant_id,user_id,cus_id,page,number,is_share,time,event from student where (cus_id = 3 and page = 3 and event = 5) order by create_time desc limit 1
2024-07-05 16:32:27.663 INFO 3264 --- [nio-8221-exec-1] com.xu.hander.MybatisLogger : SQL耗时: 3