Spring插件之PageHelper(二)的执行原理

这里我们只讲常用方式PageHelper的静态方法startPage的执行流程

一、spring容器初始化时,由于我们配置了插件PageHelper给sqlSessionFactoryBean,所以初始化工厂时会为我们载入配置

@Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource da) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(da);

        //设置分页的拦截器
        PageInterceptor pageInterceptor = new PageInterceptor();
        //创建插件需要的参数集合
        Properties properties = new Properties();
        //配置数据库 为oracle
        properties.setProperty("helperDialect", "oracle");
        //配置分页的合理化数据
        properties.setProperty("reasonable", "true");
        pageInterceptor.setProperties(properties);
        //将拦截器设置到sqlSessionFactroy中
        sqlSessionFactoryBean.setPlugins(new Interceptor[] {pageInterceptor});

        return sqlSessionFactoryBean;
    }

二、执行流程

    /**
     * 查询所有商品
     * */
    @PreAuthorize("hasAuthority('PRODUCT_LIST')")
    @Transactional(propagation = Propagation.SUPPORTS ,readOnly = true)
    public PageInfo findAllProduct(Integer pageNum,Integer pageSize){
         //1.在调用dao查询前,先调用PageHelper的静态方法
         PageHelper.startPage(pageNum, pageSize);
         //2.调用dao查询
         List<Product> products = productDao.findAllProduct();
         //3.将查询以构造方式存入到PageHelper为我们提供的分页工具类PageInfo,返回给控制层 
         PageInfo pageInfo = new PageInfo(products);
         return pageInfo;
    };

1.

静态方法startPage执行插件会创建一个Page对象 存储当前的页码和每页条数 放在threadlocal变量中,和当前线程做绑定。

public abstract class PageMethod

    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

 /**
     * 设置 Page 参数  将生成的page绑定到当前线程
     *
     * @param page
     */
    protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

  /**
     * 开始分页
     *
     * @param params
     */
    public static <E> Page<E> startPage(Object params) {
        Page<E> page = PageObjectUtil.getPageFromObject(params, true);
        //当已经执行过orderBy的时候
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        //将生成的page绑定到当前线程
        setLocalPage(page);
        return page;
    }

}

2.

由于我们在sqlSession中配置了PageInterceptor拦截器,所以sqlSession查询前会被该拦截器拦截,
拦截器会判断该查询是否需要分页,如果不需要分页,直接调用dao的sql进行执行,
如果需要分页,获取dao执行的sql语句,根据当前的数据库方言(我们这里用了oracle),得到分页的sql语句,执行改sql查询得到分页后的记录结果集

//该拦截器类,实现了mybatis的拦截器接口
public class PageInterceptor implements Interceptor 

                    //判断是否需要进行分页查询
                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);
                }

例如当配置为oracle时,会根据我们dao原有的参数,和分页参数,生成oracle的分页sql语句

public class OracleDialect extends AbstractHelperDialect

 public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120);
        if (page.getStartRow() > 0) {
            sqlBuilder.append("SELECT * FROM ( ");
        }
        if (page.getEndRow() > 0) {
            sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( ");
        }
        sqlBuilder.append(sql);
        if (page.getEndRow() > 0) {
            sqlBuilder.append(" ) TMP_PAGE WHERE ROWNUM <= ? ");
        }
        if (page.getStartRow() > 0) {
            sqlBuilder.append(" ) WHERE ROW_ID > ? ");
        }
        return sqlBuilder.toString();
    }

3.

使用得到的记录结果集 从threadlocal变量中取出page对象 把集合赋值给page对象 此时page对象已经包含pageNum pageSize total List记录集合

public abstract class AbstractHelperDialect extends AbstractDialect implements Constant

//查询万结果集后
  @Override
    public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
       //1.从线程对象中获取oage对象
        Page page = getLocalPage();
        if (page == null) {
            return pageList;
        }
        //2.将查询结果集添加到线程中的page对象中
        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());
        }
        //3.将page返回
        return page;
    }

4.

这里我们来看下page对象,可以看到page对象继承了arraylist集合,所以调用addAll可以将查询结果添加到page对象中,而继承的另一个好处就是,根据多态的原理,该结果集对象page可以直接作为返回值,赋给我们在dao中的定义好的list集合,并返回给service业务层

public class Page<E> extends ArrayList<E> implements Closeable {
    private static final long serialVersionUID = 1L;

    /**
     * 页码,从1开始
     */
    private int pageNum;
    /**
     * 页面大小
     */
    private int pageSize;
    /**
     * 起始行
     */
    private int startRow;
    /**
     * 末行
     */
    private int endRow;
    /**
     * 总数
     */
    private long total;
    /**
     * 总页数
     */
    private int pages;
    /**
     * 包含count查询
     */
    private boolean count = true;
    /**
     * 分页合理化
     */
    private Boolean reasonable;
    /**
     * 当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
     */
    private Boolean pageSizeZero;
    /**
     * 进行count查询的列名
     */
    private String countColumn;
    /**
     * 排序
     */
    private String orderBy;
    /**
     * 只增加排序
     */
    private boolean orderByOnly;

5.

当servicee收到返回值后,我们通过构造将其封装到插件为我们提供的前端显示对象pageInfo中(page对象主要用于数据库查询的封装,pageInfo主要用于前端的显示),我们可以直接将pageInfo返回给jsp页面,在jsp页面中通过el表达式调用pageInfo的get方法取值。

这里我们看到pageInfo的构造方法,对page中的值进行了获取和封装

public class PageInfo<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    //当前页
    private int pageNum;
    //每页的数量
    private int pageSize;
    //当前页的数量
    private int size;

    //由于startRow和endRow不常用,这里说个具体的用法
    //可以在页面中"显示startRow到endRow 共size条数据"

    //当前页面第一个元素在数据库中的行号
    private int startRow;
    //当前页面最后一个元素在数据库中的行号
    private int endRow;
    //总记录数
    private long total;
    //总页数
    private int pages;
    //结果集
    private List<T> list;

    //前一页
    private int prePage;
    //下一页
    private int nextPage;

    //是否为第一页
    private boolean isFirstPage = false;
    //是否为最后一页
    private boolean isLastPage = false;
    //是否有前一页
    private boolean hasPreviousPage = false;
    //是否有下一页
    private boolean hasNextPage = false;
    //导航页码数
    private int navigatePages;
    //所有导航页号
    private int[] navigatepageNums;
    //导航条上的第一页
    private int navigateFirstPage;
    //导航条上的最后一页
    private int navigateLastPage;


 public PageInfo(List<T> list) {
        this(list, 8);
    }

    /**
     * 包装Page对象
     *
     * @param list          page结果
     * @param navigatePages 页码数量
     */
    public PageInfo(List<T> list, int navigatePages) {
        if (list instanceof Page) {
            Page page = (Page) list;
            this.pageNum = page.getPageNum();
            this.pageSize = page.getPageSize();

            this.pages = page.getPages();
            this.list = page;
            this.size = page.size();
            this.total = page.getTotal();
            //由于结果是>startRow的,所以实际的需要+1
            if (this.size == 0) {
                this.startRow = 0;
                this.endRow = 0;
            } else {
                this.startRow = page.getStartRow() + 1;
                //计算实际的endRow(最后一页的时候特殊)
                this.endRow = this.startRow - 1 + this.size;
            }
        } else if (list instanceof Collection) {
            this.pageNum = 1;
            this.pageSize = list.size();

            this.pages = this.pageSize > 0 ? 1 : 0;
            this.list = list;
            this.size = list.size();
            this.total = list.size();
            this.startRow = 0;
            this.endRow = list.size() > 0 ? list.size() - 1 : 0;
        }
        if (list instanceof Collection) {
            this.navigatePages = navigatePages;
            //计算导航页
            calcNavigatepageNums();
            //计算前后页,第一页,最后一页
            calcPage();
            //判断页面边界
            judgePageBoudary();
        }
    }



猜你喜欢

转载自blog.csdn.net/houysx/article/details/80230628