Mybatis拦截器
拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,
也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。
Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。
打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。
这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?
当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,
之后可以选择是否继续执行原来的query方法。
对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
Mybatis拦截器只能拦截四种类型的接口:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
Executor拦截器的执行顺序,其他接口拦截器类似
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke
Mybatis一级缓存
PerpetualCache
基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。默认就支持一级缓存的
mybatis 拦截器定义
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 查看该方法 newExecutor,返回的是代理对象
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
executor = (Executor) interceptorChain.pluginAll(executor);
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 查看自定义插架中的方法
target = interceptor.plugin(target);
}
return target;
}
com.dn.spring.mybatis.interceptor.ExectorInterceptor#plugin
自定义 插件中必须做类型判断
@Override
public Object plugin(Object target) {
//必须判断是否是拦截的类型
if (target instanceof Executor) {
//生成代理类
return Plugin.wrap(target, this);
}
return target;
}
mybatis拦截器定义
import org.apache.ibatis.plugin.*;
@Intercepts(
//每配置一个 @Signature 则就可以拦截一个方法
// 这里指要拦截 Executor 类中的 query 方法
{@Signature(method = "query", type = Executor.class,
//根据参数的类型找对应的方法
args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})
}
)
public class ExectorInterceptor implements Interceptor {
}
org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
//@Intercepts 注解解析
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 返回代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
查看
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// executor 是代理对象,则会调入到 org.apache.ibatis.plugin.Plugin#invoke
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
插件会生成代理类,所以调用代理类方法时,会调入到 Plugin#invoke
org.apache.ibatis.plugin.Plugin#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取需要拦截的方法,也就是插件上定义的方法,例如:插件上定义的 method = "query"
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 调用自定义插件中的 intercept 方法
return interceptor.intercept(new Invocation(target, method, args));
}
//调用被代理的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
见:com.dn.spring.mybatis.interceptor.ExectorInterceptor 插件的定义
查看源码
org.apache.ibatis.executor.SimpleExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
见 com.dn.spring.mybatis.interceptor.PageInterceptor
插件,拦截 StatementHandler 类
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
//循环插件,调用自定义插件中的 plugin 方法
target = interceptor.plugin(target);
}
return target;
}
com.dn.spring.mybatis.interceptor.PageInterceptor#plugin
这里需要在插件中需要判断调用的目标对象是不是需要处理的类,必须要判断的,不判断时,返回的对象就错了
@Override
public Object plugin(Object target) {
if (target instanceof RoutingStatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
分页插件定义
org.apache.ibatis.executor.SimpleExecutor#doQuery
查看该方法
stmt = prepareStatement(handler, ms.getStatementLog());
org.apache.ibatis.executor.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 对 prepare 方法进行拦截
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
org.apache.ibatis.executor.statement.BaseStatementHandler#prepare
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
// 分页时,需要更改 sql 的语句
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
见分页插件 com.dn.spring.mybatis.interceptor.PageInterceptor
分页逻辑
- 拿到被代理对象RoutingStatementHandler
- 拿到被代理对象的private final StatementHandler delegate; PreparedStatementHandler
- 拿到PreparedStatementHandler里面的BoundSql属性
- 修改BoundSql属性
对查询结果进行缓存 mybatis插件定义
org.apache.ibatis.executor.SimpleExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//执行查询
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
org.apache.ibatis.executor.statement.SimpleStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
//查询结果返回值:handleResultSets 处理
return resultSetHandler.<E>handleResultSets(statement);
}
见:com.dn.spring.mybatis.interceptor.ResultSetCacheInterceptor 缓存查询结果
见:com.dn.spring.mybatis.interceptor.ExectorInterceptor 查询缓存结果