mysql 拦截器_Mybatis之拦截器Interceptor

使用mybatis时用PageHelper进行分页,用到了PageInterceptor,借此了解下mybatis的interceptor。Mybatis的版本是3.4.6,MybatisHelper的版本是5.1.3。

1、PageInterceptor

先上一段代码,如下List-1:

List-1

@Test

public void testPage() {

PageHelper.startPage(2, 3);

List all = personMapper.findAll();

PageInfo personPageInfo = new PageInfo<>(all,3);

log.info(all.toString());

int pageNum = personPageInfo.getPageNum();

int pageSize = personPageInfo.getSize();

int pages = personPageInfo.getPages();

log.info("pageNum:"+pageNum+" size:"+ pageSize +" pages:"+ pages);

}

List-1中,查询所有的Person,不过分页查询,注意使用PageHelper后,得到的List类型的all是com.github.pagehelper.Page,它继承了JDK的ArrayList,如下List-2所示,我在使用时一开始也以为是JDK的List实现,直到在看PageInfo时出现一些情况,深入了解后才发现是PageHelper继承的ArrayList:

List-2

public class Page extends ArrayList implements Closeable {

private static final long serialVersionUID = 1L;

private int pageNum;

private int pageSize;

private int startRow;

private int endRow;

private long total;

private int pages;

private boolean count;

private Boolean reasonable;

private Boolean pageSizeZero;

private String countColumn;

private String orderBy;

private boolean orderByOnly;

......

mybatis的Interceptor如下List-3所示,PageInterceptor实现了这个接口:

List-3

public interface Interceptor {

Object intercept(Invocation invocation) throws Throwable;

Object plugin(Object target);

void setProperties(Properties properties);

}

来看PageInterceptor如何实现intercept接口的,如下List-4

List-4

@Override

public Object intercept(Invocation invocation) throws Throwable {

try {

Object[] args = invocation.getArgs();

MappedStatement ms = (MappedStatement) args[0];

Object parameter = args[1];

RowBounds rowBounds = (RowBounds) args[2];

ResultHandler resultHandler = (ResultHandler) args[3];

Executor executor = (Executor) invocation.getTarget();

CacheKey cacheKey;

BoundSql boundSql;

//由于逻辑关系,只会进入一次

if(args.length == 4){

//4 个参数时

boundSql = ms.getBoundSql(parameter);

cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);

} else {

//6 个参数时

cacheKey = (CacheKey) args[4];

boundSql = (BoundSql) args[5];

}

List resultList;

//调用方法判断是否需要进行分页,如果不需要,直接返回结果

if (!dialect.skip(ms, parameter, rowBounds)) {

//反射获取动态参数

String msId = ms.getId();

Configuration configuration = ms.getConfiguration();

Map additionalParameters = (Map) additionalParametersField.get(boundSql);

//判断是否需要进行 count 查询

if (dialect.beforeCount(ms, parameter, rowBounds)) {

String countMsId = msId + countSuffix;

Long count;

//先判断是否存在手写的 count 查询

MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);

if(countMs != null){

count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);

} else {

countMs = msCountMap.get(countMsId);

//自动创建

if (countMs == null) {

//根据当前的 ms 创建一个返回值为 Long 类型的 ms

countMs = MSUtils.newCountMappedStatement(ms, countMsId);

msCountMap.put(countMsId, countMs);

}

count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);

}

//处理查询总数

//返回 true 时继续分页查询,false 时直接返回

if (!dialect.afterCount(count, parameter, rowBounds)) {

//当查询总数为 0 时,直接返回空的结果

return dialect.afterPage(new ArrayList(), parameter, rowBounds);

}

}

//判断是否需要进行分页查询

if (dialect.beforePage(ms, parameter, rowBounds)) {

//生成分页的缓存 key

CacheKey pageKey = cacheKey;

//处理参数对象

parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);

//调用方言获取分页 sql

String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);

BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);

//设置动态参数

for (String key : additionalParameters.keySet()) {

pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));

}

//执行分页查询

resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);

} else {

//不执行分页的情况下,也不执行内存分页

resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);

}

} else {

//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页

resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);

}

return dialect.afterPage(resultList, parameter, rowBounds);

} finally {

dialect.afterAll();

}

}

我们来分析下List-4的内容,"if (!dialect.skip(ms, parameter, rowBounds)) {"判断是否需要进行分页,这个dialect就是PageHelper,如下List-5,有兴趣可以看下"pageParams.getPage(parameterObject, rowBounds);",

List-5

public class PageHelper extends PageMethod implements Dialect {

private PageParams pageParams;

private PageAutoDialect autoDialect;

@Override

public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {

if(ms.getId().endsWith(MSUtils.COUNT)){

throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");

}

Page page = pageParams.getPage(parameterObject, rowBounds);

if (page == null) {

return true;

} else {

//设置默认的 count 列

if(StringUtil.isEmpty(page.getCountColumn())){

page.setCountColumn(pageParams.getCountColumn());

}

autoDialect.initDelegateDialect(ms);

return false;

}

}

...

List-4中如果不需要分页,则直接调用executor的query方法。需要分页情况下,首先会看是否需要进行count查询——List-4中的"if (dialect.beforeCount(ms, parameter, rowBounds))",dialect.beforeCount的最终实现是在AbstractHelperDialect的beforeCount方法,如下List-6,getLocalPage()调用PageHelper.getLocalPage(),得到com.github.pagehelper.Page——isOrderByOnly默认返还false,isCount默认返还true。

List-6

public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {

...

@Override

public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {

Page page = getLocalPage();

return !page.isOrderByOnly() && page.isCount();

}

...

List-4中,需要count查询后,判断手写的count已存在,不存在则调用mybatis的builder等工具类构造,之后进行count查询,得到结果后调用dialect的afterCount方法,实现在AbstractHelperDialect的afterCount方法,如下List-7,getLocalPage()获取PageInfo中的Page,之后设置total。在分页的时候,我们会对PageHelper进行pageSize的设置,所以只要count的结果大于0,就会返还true。

List-7

@Override

public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {

Page page = getLocalPage();

page.setTotal(count);

if (rowBounds instanceof PageRowBounds) {

((PageRowBounds) rowBounds).setTotal(count);

}

//pageSize < 0 的时候,不执行分页查询

//pageSize = 0 的时候,还需要执行后续查询,但是不会分页

if (page.getPageSize() < 0) {

return false;

}

return count > 0;

}

List-4中查询到count,且大于0,则继续后续,先判断是否需要进行分页查询——List-4中的"dialect.beforePage(ms, parameter, rowBounds)",实现是在AbstractHelperDialect中,这里不再深入这点,只要我们的pageSize设置大于0,该方法默认是返还true的。

进行分页查询,会调用AbstractHelperDialect的getPageSql方法获取数据库执行的sql,以mysql为例子,AbstractHelperDialect在调用子类MySqlDialect的getPageSql方法,如下List-8,会在sql的最后加上limit语句。AbstractHelperDialect的子类有很多种,对应不同的数据库,这里使用了模板设计模式。之后再将这个sql交给executor执行,达到分页的操作。有些人说mybatis的分页查询插件底层上是全部查出到内存之后进行切分,但是我看到的是通过limit进行分页,没有什么问题的,控制台打印的sql也是带有limit的。

List-8

@Override

public String getPageSql(String sql, Page page, CacheKey pageKey) {

StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);

sqlBuilder.append(sql);

if (page.getStartRow() == 0) {

sqlBuilder.append(" LIMIT ? ");

} else {

sqlBuilder.append(" LIMIT ?, ? ");

}

pageKey.update(page.getPageSize());

return sqlBuilder.toString();

}

List-4中,分页查询到结果后,调用"dialect.afterPage(resultList, parameter, rowBounds)",即AbstractHelperDialect的afterPage方法,如下List-9,从PageHelper中得到Page,之后将分页查询的结果放入到Page中。

List-9

@Override

public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {

Page page = getLocalPage();

if (page == null) {

return pageList;

}

page.addAll(pageList);

if (!page.isCount()) {

page.setTotal(-1);

} else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {

page.setTotal(pageList.size());

} else if(page.isOrderByOnly()){

page.setTotal(pageList.size());

}

return page;

}

List-4中最后的finally块调用dialect.afterAll(),我们来看下实现,如下的PageHelper的afterAll(),清理各项清理。

List-10

@Override

public void afterAll() {

//这个方法即使不分页也会被执行,所以要判断 null

AbstractHelperDialect delegate = autoDialect.getDelegate();

if (delegate != null) {

delegate.afterAll();

autoDialect.clearDelegate();

}

clearPage();

}

最终,这个PageInterceptor的intercept方法返还的是自己定义的Page。

2、PageHelper

来看看PageHelper,它的父类PageMethod,如下的List-10,使用ThreadLocal来存储Page,如果熟悉JDK的ThreadLocal那么,对其要注意的点,在PageInterceptor的使用时也要注意,这里不再深究。

List-10

public abstract class PageMethod {

protected static final ThreadLocal LOCAL_PAGE = new ThreadLocal();

protected static boolean DEFAULT_COUNT = true;

/**

* 设置 Page 参数

*

* @param page

*/

protected static void setLocalPage(Page page) {

LOCAL_PAGE.set(page);

}

/**

* 获取 Page 参数

*

* @return

*/

public static Page getLocalPage() {

return LOCAL_PAGE.get();

}

/**

* 移除本地变量

*/

public static void clearPage() {

LOCAL_PAGE.remove();

}

...

3、在哪里调用Interceptor

什么时候调用Interceptor呢,来看mybatis的Configuration的几个方法,这几个方法最后都调用了interceptorChain,如List-12所示,使用责任链模式,调用plugin方法,最后调用Interceptor的intercept方法。

List-11

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);

parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);

return parameterHandler;

}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,

ResultHandler resultHandler, BoundSql boundSql) {

ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);

resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

return resultSetHandler;

}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

return statementHandler;

}

public Executor newExecutor(Transaction transaction) {

return newExecutor(transaction, defaultExecutorType);

}

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;

}

List-12

public class InterceptorChain {

private final List interceptors = new ArrayList();

public Object pluginAll(Object target) {

for (Interceptor interceptor : interceptors) {

target = interceptor.plugin(target);

}

return target;

}

public void addInterceptor(Interceptor interceptor) {

interceptors.add(interceptor);

}

public List getInterceptors() {

return Collections.unmodifiableList(interceptors);

}

}

思考,插件还可以做其它什么呢,我们可以用来进行sql性能耗时的统计,或者对更新操作统一的加上更新者、时间等。

Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值