MyBatis攔截器介紹
MyBatis 允許你在已映射語(yǔ)句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(diào)用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
我們看到了可以攔截Executor接口的部分方法唇跨,比如update,query,commit,rollback等方法樊零,還有其他接口的一些方法等。
總體概括為:
攔截執(zhí)行器的方法
攔截參數(shù)的處理
攔截結(jié)果集的處理
攔截Sql語(yǔ)法構(gòu)建的處理
MyBatis攔截器使用(分頁(yè)插件)
Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class PageHelper implements Interceptor {
private static final Logger logger = Logger.getLogger(PageHelper.class);
public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();
/**
* 開始分頁(yè)
*
* @param pageNum
* @param pageSize
*/
public static void startPage(int pageNum, int pageSize) {
localPage.set(new Page(pageNum, pageSize));
}
/**
* 結(jié)束分頁(yè)并返回結(jié)果孽文,該方法必須被調(diào)用驻襟,否則localPage會(huì)一直保存下去,直到下一次startPage
*
* @return
*/
public static Page endPage() {
Page page = localPage.get();
localPage.remove();
return page;
}
public Object intercept(Invocation invocation) throws Throwable {
if (localPage.get() == null) {
return invocation.proceed();
}
if (invocation.getTarget() instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
// 分離代理對(duì)象鏈(由于目標(biāo)類可能被多個(gè)攔截器攔截芋哭,從而形成多次代理塑悼,通過下面的兩次循環(huán)
// 可以分離出最原始的的目標(biāo)類)
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = SystemMetaObject.forObject(object);
}
// 分離最后一個(gè)代理對(duì)象的目標(biāo)類
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = SystemMetaObject.forObject(object);
}
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
//分頁(yè)信息if (localPage.get() != null) {
Page page = localPage.get();
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
// 分頁(yè)參數(shù)作為參數(shù)對(duì)象parameterObject的一個(gè)屬性
String sql = boundSql.getSql();
// 重寫sql
String pageSql = buildPageSql(sql, page);
//重寫分頁(yè)sql
metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
Connection connection = (Connection) invocation.getArgs()[0];
// 重設(shè)分頁(yè)參數(shù)里的總頁(yè)數(shù)等
setPageParameter(sql, connection, mappedStatement, boundSql, page);
// 將執(zhí)行權(quán)交給下一個(gè)攔截器
return invocation.proceed();
} else if (invocation.getTarget() instanceof ResultSetHandler) {
Object result = invocation.proceed();
Page page = localPage.get();
page.setResult((List) result);
return result;
}
return null;
}
/**
* 只攔截這兩種類型的
* <br>StatementHandler
* <br>ResultSetHandler
*
* @param target
* @return
*/
public Object plugin(Object target) {
if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
public void setProperties(Properties properties) {
}
/**
* 修改原SQL為分頁(yè)SQL
*
* @param sql
* @param page
* @return
*/
private String buildPageSql(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(100);
pageSql.append(sql);
pageSql.append(" limit " + page.getStartRow() + "," + page.getPageSize());
return pageSql.toString();
}
/**
* 獲取總記錄數(shù)
*
* @param sql
* @param connection
* @param mappedStatement
* @param boundSql
* @param page
*/
private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
BoundSql boundSql, Page page) {
// 記錄總記錄數(shù)
String countSql = "select count(0) from (" + sql + ") as total";
PreparedStatement countStmt = null;
ResultSet rs = null;
try {
countStmt = connection.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
rs = countStmt.executeQuery();
int totalCount = 0;
if (rs.next()) {
totalCount = rs.getInt(1);
}
page.setTotal(totalCount);
int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
page.setPages(totalPage);
} catch (SQLException e) {
logger.error("Ignore this exception", e);
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Ignore this exception", e);
}
try {
countStmt.close();
} catch (SQLException e) {
logger.error("Ignore this exception", e);
}
}
}
/**
* 代入?yún)?shù)值
*
* @param ps
* @param mappedStatement
* @param boundSql
* @param parameterObject
* @throws SQLException
*/
private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
Object parameterObject) throws SQLException {
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler.setParameters(ps);
}
/**
* Description: 分頁(yè)
* Author: liuzh
* Update: liuzh(2014-04-16 10:56)
*/
@Data
public static class Page<E> {
private int pageNum;
private int pageSize;
private int startRow;
private int endRow;
private long total;
private int pages;
private List<E> result;
public Page(int pageNum, int pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
this.endRow = pageNum * pageSize;
}
}
}
//configuration.xml添加配置
<plugins>
<plugin interceptor="mybatis.plugin.PageHelper"></plugin>
</plugins>
源碼分析
//將攔截器set到configuration
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
//獲取SqlSession(Configuration類下)
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//獲取SqlSession
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);
//獲取executor,接下來是具體代碼
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//添加攔截器
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
//InterceptorChain
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//然后走自己實(shí)現(xiàn)的代碼楷掉,如果這里攔截的是Executor則調(diào)用plugin()方法
public Object plugin(Object target) {
if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
//生成代理并且返回
public static Object wrap(Object target, Interceptor interceptor) {
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;
}
//如果被代理則Plugin類invoke()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//自己實(shí)現(xiàn)的intercept()
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
注意:
在每個(gè)攔截器的intercept方法內(nèi),最后一個(gè)語(yǔ)句一定是return invocation.proceed()霞势。invocation.proceed()只是簡(jiǎn)單的調(diào)用了下target的對(duì)應(yīng)方法烹植,如果target還是個(gè)代理,就又回到了上面的Plugin.invoke方法了愕贡。這樣就形成了攔截器的調(diào)用鏈推進(jìn)草雕。