资讯专栏INFORMATION COLUMN

从 PageHelper 学到的不侵入 Signature 的 AOP

trigkit4 / 1525人阅读

摘要:从学到的不侵入的前言最近搭新项目框架,之前的拦截器都是自己写的,一般是有个类型做判断是否增加分页。发现开源项目不需要侵入方法的就可以做分页,特此来源码分析一下。

从 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
            

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74179.html

相关文章

  • 造个轮子,我学到了什么

    摘要:阅读原文造个轮子我学到了什么听说的最多的是不是不要重复的造轮子不要被这句话蒙骗了,这句话应该还没说完整,在什么情况下不要造轮子实际项目中由于工期和质量原因,肯定不希望你造轮子,你造轮子花费时间且质量不如现有的轮子。 阅读原文:造个轮子,我学到了什么 听说的最多的是不是不要重复的造轮子?不要被这句话蒙骗了,这句话应该还没说完整,在什么情况下不要造轮子?实际项目中由于工期和质量原因,肯定不...

    Acceml 评论0 收藏0
  • 手把手教你如何优雅使用Aop记录带参数复杂Web接口日志

    摘要:几乎每一个接口被调用后,都要记录一条跟这个参数挂钩的特定的日志到数据库。我最终采用了的方式,采取拦截的请求的方式,来记录日志。所有打上了这个注解的方法,将会记录日志。那么如何从众多可能的参数中,为当前的日志指定对应的参数呢。 前言 不久前,因为需求的原因,需要实现一个操作日志。几乎每一个接口被调用后,都要记录一条跟这个参数挂钩的特定的日志到数据库。举个例子,就比如禁言操作,日志中需要记...

    Loong_T 评论0 收藏0
  • 前端装饰器,AOP使用

    摘要:中的装饰在前端编程中,我们可以采用装饰器,来实现编程。装饰器使用我们先建立一个简单的类,这个类的作用,就是在执行的时候,打印出。至此,一个简单的装饰器范例已经完成。 什么是装饰器? 了解AOP 在学习js中的装饰器之间,我们需要了解AOP(面向切面编程)编程思想。 AOP是一种可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是Go...

    lewinlee 评论0 收藏0
  • 基于SpringBoot后台管理系统(启动类解析,开源世界真好)(一)

    摘要:目前只是一个后台模块,希望自己技能增强到一定时,可以把的融合进来。目录第一站,分析了启动类。看见没,这个也是配置类,它声明了视图解析器地域解析器以及静态资源的位置,想起来没,就是前置,后置。程序启动类我们点击源码看看。 Guns基于SpringBoot,致力于做更简洁的后台管理系统,完美整合springmvc + shiro + 分页插件PageHelper + 通用Mapper + ...

    SwordFly 评论0 收藏0
  • Java 代理模式与 AOP

    摘要:本文首发于作者最近在学,研究了下和代理模式,写点心得和大家分享下。所以下面来重点分析下代理模式。这里代理模式分为静态代理和动态代理两种,我们分别来看下。代理模式,代理,意味着有一方代替另一方完成一件事。 本文首发于 https://jaychen.cc作者 jaychen 最近在学 Spring,研究了下 AOP 和代理模式,写点心得和大家分享下。 AOP 先说下AOP,AOP 全称 ...

    jk_v1 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<