一。 Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler四个类里面的方法。
二 。可以拦截四个对象中某个对象中的具体某一个方法
@Intercepts({@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class})})
public class MyBatisInterceptor implements Interceptor {
/**
* 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
return null;
}
/**
* plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
*
* 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
* 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
*/
@Override
public Object plugin(Object target) {
return null;
}
/**
* 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
*/
@Override
public void setProperties(Properties properties) {
}
}
Intercepts 的声明可以是多个 Signature type 拦截的是那个类 method 是类中的那个方法 args 是该方法的参数
三。先看下源码
package org.apache.ibatis.executor;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement var1, Object var2) throws SQLException;
List query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
List query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
Cursor queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
List flushStatements() throws SQLException;
void commit(boolean var1) throws SQLException;
void rollback(boolean var1) throws SQLException;
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
boolean isCached(MappedStatement var1, CacheKey var2);
void clearLocalCache();
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class> var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
}
package org.apache.ibatis.executor.parameter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}
package org.apache.ibatis.executor.statement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.ResultHandler;
public interface StatementHandler {
Statement prepare(Connection var1, Integer var2) throws SQLException;
void parameterize(Statement var1) throws SQLException;
void batch(Statement var1) throws SQLException;
int update(Statement var1) throws SQLException;
List query(Statement var1, ResultHandler var2) throws SQLException;
Cursor queryCursor(Statement var1) throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
package org.apache.ibatis.executor.resultset;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.apache.ibatis.cursor.Cursor;
public interface ResultSetHandler {
List handleResultSets(Statement var1) throws SQLException;
Cursor handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}
各个接口中的实现功能大概就和table 描述一致我们只要根据业务去针对性的进行拦截处理就可以
基于mybatis plus 写的一个简单的租户分割
@Slf4j
@Intercepts({@Signature(
type = StatementHandler .class,
method = "prepare",
args = {Connection.class, Integer.class})})
public class MyBatisInterceptor extends AbstractSqlParserHandler implements Interceptor {
public MyBatisInterceptor() {
}
/**
* 代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
*
*
* 等于是重写这个方法了 把参数拿过来然后 返回原方法的result
*
*
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
//映射工具
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
//中间去判断多层
this.sqlParser(metaObject);
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
//要是查询操作 并且 要求 StatementType 非存储过程 (属于jDBC Statement(非预编译) prepareStatement(预编译) CallableStatement 调用存储过程的statement)
if(SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement.getStatementType()){
//获取到执行的sql
BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String tentId = request.getHeader("tentId");
String originalSql = boundSql.getSql();
//可能出现sql 注入验证下参数
metaObject.setValue("delegate.boundSql.sql",originalSql +"and tent_id = " +tentId);
}
return invocation.proceed();
}
/**
* plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理
*
* 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
* 当返回的是当前对象的时候 就不会调用intercept方法,相当于当前拦截器无效
*/
@Override
public Object plugin(Object target) {
return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
}
/**
* 用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
*/
@Override
public void setProperties(Properties properties) {
}
}
声明时候bean 的加载顺序决定 plugin 的加载顺序,在spring boot + mybatis plus 中我的写法
/**
* @author chenkang
*/
@Configuration
@MapperScan(value = {"com.chenkang.tenant.mapper"})
public class MybatisPlusConfig {
@Bean
@Order(1)
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Bean
@Order(2)
public MyBatisInterceptor myBatisInterceptor() {
return new MyBatisInterceptor();
}
}