MyBatis拦截器
拦截器的作用
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed),MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
ParameterHandler (getParameterObject, setParameters),负责对用户传递的参数转换成JDBC Statement 所需要的参数。
ResultSetHandler (handleResultSets, handleOutputParameters),负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
StatementHandler (prepare, parameterize, batch, update, query),封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为,
因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
实际的应用:在很多业务场景下我们需要去拦截sql,达到不入侵原有代码业务处理一些东西,比如:分页操作,数据权限过滤操作,SQL执行时间性能监控等等,这里我们就可以用到Mybatis的拦截器Interceptor
原理解析:https://blog.csdn.net/weixin_39494923/article/details/91534658
MyBatis的执行流程图。
MyBatis的架构图
实现一个简单的拦截器
定义 Mybatis 拦截器只需要实现 Interceptor 类, 并通过注解设置需要拦截的对象即可:
type=>mybatis 的四大对象:[ParameterHandler],[ResultSetHandler],[StatementHandler],[Executor].
method => 四大对象中需要拦截的方法, 具体可以直接看源码. 例如 Executor 类的 update 方法就经常用到.
args=> 拦截方法所需要的参数, 如果不清楚的话也可以直接看源码. 例如 update 方法需要 MappedStatement 类型和 Object 类型的两个参数.
intercept(Invocation invocation)=> 此方法内写具体的拦截逻辑.
plugin()=> 没什么特殊要求的话直接写成如下形式即可, 意思是把定义的插件注册进插件链里面 (越晚注册越先执行).
需求:定义拦截器类 实现显示sql的输出量仅为1条,拦截到执行的sql再对该sql进行改写
自定义一个拦截器:
package com.wx.currencymapper.interceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.Properties;
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,Integer.class })})
@Component
public class SqlStatusInterceptor implements Interceptor {
/**拦截sql,实现显示sql的输出量仅为1条,思路:拦截到执行的sql再对该sql进行改写*/
private static final Logger log=LoggerFactory.getLogger(SqlStatusInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
/**具体的拦截逻辑*/
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
//获取sql
String sql= String.valueOf(metaStatementHandler.getValue("delegate.boundSql.sql"));
//添加limit条件
sql="select * from (" + sql + ") as temp limit 1";
//重新设置sql
metaStatementHandler.setValue("delegate.boundSql.sql",sql);
return invocation.proceed();
}
/**
* 把拦截器插件注册进插件链里面,越晚注册越先执行
* @param o
* @return
*/
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
String dialect = properties.getProperty("dialect");
log.info("mybatis intercept dialect:{}", dialect);
}
}
不使用拦截器查询:
使用拦截器查询:
再来一个需求:自定义拦截器实现在新增/更新操作时动态为sql注入creationDate时间字段
先自定义一个注解:
package com.wx.currencymapper.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义拦截器时运行时注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface CreateTime {
String value() default "";
}
其次编写拦截器,注意拦截的方法
package com.wx.currencymapper.interceptor;
import com.wx.currencymapper.annotation.CreateTime;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.Properties;
@Intercepts({ @Signature(type = Executor.class, method = “update”, args = { MappedStatement.class,Object.class })})
@Component
public class SqlInsertInterceptor implements Interceptor {
private static final Logger log=LoggerFactory.getLogger(SqlInsertInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// 获取 SQL 命令
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
log.info("获取到的sql命令为:{}",sqlCommandType);
// 获取参数
Object parameter = invocation.getArgs()[1];
if (parameter != null) {
// 获取成员变量
Field[] declaredFields = parameter.getClass().getDeclaredFields();
for (Field field : declaredFields) {
if (field.getAnnotation(CreateTime.class) != null) {
if (SqlCommandType.INSERT.equals(sqlCommandType)) { // insert 语句插入 createTime
field.setAccessible(true);
if (field.get(parameter) == null) {
field.set(parameter, new Date());
}
}
}
/* if (field.getAnnotation(UpdateTime.class) != null) { // insert 或 update 语句插入 updateTime
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
field.setAccessible(true);
if (field.get(parameter) == null) {
field.set(parameter, new Date());
}
}
}*/
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
}
SQL的编写:
<insert id="insertOrders" parameterType="com.wx.currencymapper.domain.Orders">
INSERT INTO
orders(user_id,number,createtime,note)
VALUES(1,#{number},#{createtime},#{note})
</insert>
测试:
@RequestMapping("/insertOrders")
@ResponseBody
public void insertOrder(Orders orders){
orders.setNumber("34");
orders.setNote("啊哈哈啊哈");
ordersService.insertOrders(orders);
}
成功添加:
上一篇: Spring MVC