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 查詢緩存結果