signaturehelper.php,从 PageHelper 学到的不侵入 Signature 的 AOP

从 PageHelper 学到的不侵入 Signature 的 AOP

前言

最近搭新项目框架,之前 Mybatis 的拦截器都是自己写的,一般是有个 Page 类型做判断是否增加分页 sql。但是这样同样的业务开放给页面和 api 可能要写两个,一种带分页类型 Page 一种不带分页。

发现开源项目 PageHelper 不需要侵入方法的 Signature 就可以做分页,特此来源码分析一下。

P.S: 看后面的源码分析最好能懂 mybatis 得拦截器分页插件原理

PageHelper 的简答使用

public PageInfo listRpmDetailByCondition(String filename, Date startTime, Date endTime,

Integer categoryId, Integer ostypeId, Integer statusId,

Integer pageNo, Integer pageSize) {

PageHelper.startPage(pageNo, pageSize);

List result = rpmDetailMapper.listRpmDetailByCondition(filename, startTime, endTime, categoryId,

ostypeId, statusId);

return new PageInfo(result);

}

PageHelper.startPage 解析

public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {

Page page = new Page(pageNum, pageSize, count);

page.setReasonable(reasonable);

page.setPageSizeZero(pageSizeZero);

Page oldPage = getLocalPage();

if (oldPage != null && oldPage.isOrderByOnly()) {

page.setOrderBy(oldPage.getOrderBy());

}

setLocalPage(page);

return page;

}

protected static final ThreadLocal LOCAL_PAGE = new ThreadLocal();

protected static void setLocalPage(Page page) {

LOCAL_PAGE.set(page);

}

可以看出在真正使用分页前,生成了一个 page 对象,然后放入了 ThreadLocal 中,这个思想很巧妙,利用每个请求 Web 服务,每个请求由一个线程处理的原理。利用线程来决定是否进行分页。

是否用 ThreadLocal 来判断是否分页的猜想?

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

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

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

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

//查询总数

Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);

//处理查询总数,返回 true 时继续分页查询,false 时直接返回

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

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

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

}

}

resultList = ExecutorUtil.pageQuery(dialect, executor,

ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);

} else {

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

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

}

@Override

private PageParams pageParams;

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;

}

}

/**

* 获取分页参数

*

* @param parameterObject

* @param rowBounds

* @return

*/

public Page getPage(Object parameterObject, RowBounds rowBounds) {

Page page = PageHelper.getLocalPage();

if (page == null) {

if (rowBounds != RowBounds.DEFAULT) {

if (offsetAsPageNum) {

page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount);

} else {

page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount);

//offsetAsPageNum=false的时候,由于PageNum问题,不能使用reasonable,这里会强制为false

page.setReasonable(false);

}

if(rowBounds instanceof PageRowBounds){

PageRowBounds pageRowBounds = (PageRowBounds)rowBounds;

page.setCount(pageRowBounds.getCount() == null || pageRowBounds.getCount());

}

} else if(parameterObject instanceof IPage || supportMethodsArguments){

try {

page = PageObjectUtil.getPageFromObject(parameterObject, false);

} catch (Exception e) {

return null;

}

}

if(page == null){

return null;

}

PageHelper.setLocalPage(page);

}

//分页合理化

if (page.getReasonable() == null) {

page.setReasonable(reasonable);

}

//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果

if (page.getPageSizeZero() == null) {

page.setPageSizeZero(pageSizeZero);

}

return page;

}

/**

* 获取 Page 参数

*

* @return

*/

public static Page getLocalPage() {

return LOCAL_PAGE.get();

}

果然如此,至此真相已经揭开。

怎么保证之后的 sql 不使用分页呢?

如: 先查出最近一个月注册的 10 个用户,再根据这些用户查出他们所有的订单。第一次查询需要分页,第二次并不需要

来看看作者怎么实现的?

try{

# 分页拦截逻辑

} finally {

dialect.afterAll();

}

@Override

public void afterAll() {

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

AbstractHelperDialect delegate = autoDialect.getDelegate();

if (delegate != null) {

delegate.afterAll();

autoDialect.clearDelegate();

}

clearPage();

}

/**

* 移除本地变量

*/

public static void clearPage() {

LOCAL_PAGE.remove();

}

总结逻辑

将分页对象放入 ThreadLocal

根据 ThreadLocal 判断是否进行分页

无论分页与不分页都需要清除 ThreadLocal

备注

com.github.pagehelper

pagehelper

5.1.8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值