1. 程式人生 > >Spring外掛之PageHelper(二)的執行原理

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();
        }
    }



相關推薦

Spring外掛PageHelper執行原理

這裡我們只講常用方式PageHelper的靜態方法startPage的執行流程 一、spring容器初始化時,由於我們配置了外掛PageHelper給sqlSessionFactoryBean,所以初始化工廠時會為我們載入配置 @Bean publi

Spring Boot jpa資料庫

關於配置application.yml spring: profiles: active: master datasource: driver-class-name: co

Spring MVC 的 研發

eight 對象 files tin servlet映射 資源 研發 領域 延遲 二、web.xml的簡單配置介紹1 1、啟動Web項目時,容器回去讀web.xml配置文件裏的兩個節點<context-param>和<listener

Spring Cloud探索——Spring Cloud Eureka

2.1 什麼是服務註冊與發現 在服務治理框架中,通常都會構建一個註冊中心,每個服務單元向註冊中心登記自己提供的服務,包括服務的主機與埠號、服務版本號、通訊協議等一些附加資訊。註冊中心按照服務名分類組織服務清單,同時還需要以心跳檢測的方式去監測清單中的服務是否可用,若不可用需要從服務清單中剔除,以

大資料scala --- 對映,元組,簡單類,內部類,物件Object,Idea中安裝scala外掛,trait特質[介面],包和包的匯入

一、對映<Map> ----------------------------------------------------- 1.建立一個不可變的對映Map<k,v> ==> Map(k -> v) scala> val map

spring boot 整合 jpa -- 資料操作

spring boot 整合 jpa (一) – 之基礎配置 https://blog.csdn.net/qq_41463655/article/details/82939481 spring boot 整合 jpa (三) – 之表關係對映 https://blog.csdn.net/

spring-AOP實現原理AspectJ註解方式

在上一篇spring-AOP(一)實現原理我們瞭解瞭如何使用ProxyFactory來建立AOP代理物件,但其過程需要實現一些介面,並且需要一些比較複雜的配置。因此,在spring2.0之後,提供了一種較為便利的方式。 使用@Aspect註解宣告一個切面類,之後通過@EnableAspectJAutoProx

Spring原始碼解讀Spring MVC HandlerMapping元件

一、HandlerMapping HandlerMapping作用是根據request找到相應的處理器Handler和Interceptors,並將Handler和Interceptors封裝成HandlerExecutionChain 物件返回。Handler

非同步程式設計學習-通過Synchronize實現執行緒安全的多執行

本文是非同步程式設計學習之路(二)-通過Synchronize實現執行緒安全的多執行緒,若要關注前文,請點選傳送門: 非同步程式設計學習之路(一)-通過Thread實現簡單多執行緒(執行緒不安全) 上篇我們通過Thread實現了幾種執行緒不安全的多執行緒寫法,導致最後的結果與預期的值不一樣。

Kotlin的Spring:AOP面向切面程式設計

AOP(面向切面程式設計) AOP是OOP(面向物件程式設計)的延續,但是它和麵向物件的縱向程式設計不同,它是一個橫向的切面式的程式設計。可以理解為oop就是一根柱子,如果需要就繼續往上加長,而aop則是在需要的地方把柱子切開,在中間加上一層,再把柱子完美

muduo網路庫學習EventLoop:程序執行wait/notify 和 EventLoop::runInLoop

// 事件迴圈,該函式不能跨執行緒呼叫 // 只能在建立該物件的執行緒中呼叫void EventLoop::loop() {// 斷言當前處於建立該物件的執行緒中  assertInLoopThread();     while (!quit_)     {         pollReturnTime_ =

SPRING原始碼學習

     上一篇,已經對IOC容器的初始化過程有個大體認識,接著看IOC容器的依賴注入。       依賴注入的觸發是在使用者第一次向容器索要Bean時才觸發,當然也可以設定lazy-init讓容器提前完成Bean的預例項化,預例項化是在初始化過程中完成      我

黑馬程式設計師——java基礎拾遺執行 執行緒同步、執行緒通訊

執行緒安全的概念:當多個執行緒同時執行一段程式碼時,如果結果和單執行緒執行時一致,而且其他變數也和預期的一致,說明是這段程式碼是執行緒安全的。但是,多執行緒執行的過程中會出現單執行緒時候不會出現的問題,大多出現在多個執行緒同時操作全域性變數或者靜態變數的時候。當出現這種

elasticsearch高階配置----執行緒池設定

一個Elasticsearch節點會有多個執行緒池,但重要的是下面四個:  索引(index):主要是索引資料和刪除資料操作(預設是cached型別)  搜尋(search):主要是獲取,統計和搜尋操作(預設是cached型別)  批量操作(bulk):主要是對索引的批量操作(預設是cached型別)  更

執行執行緒互動互斥與同步

首先我們通過一個有意思的案例來引入由於執行緒爭用條件造成的一些嚴重的問題。 下面的程式碼簡單來說是初始化多個能量盒子,每個盒子所含初始能量相同,這樣總能量就固定了。開設多個執行緒將這些盒子的能量相互轉移,在轉移過程就出現了問題。 package disappearEner

Spring 學習Spring 中的AOP():事務通知

AspectJ 目前,spring 框架中我們可以使用基於 AspectJ 註解或者是基於XML配置的 AOP(主流是使用 AspectJ ,簡單,方便) 如何配置AspectJ 簡單理解,Asp

Android外掛化學習ClassLoader完全解析

Java程式碼都是寫在Class裡面的,程式執行在虛擬機器上時,虛擬機器需要把需要的Class載入進來才能建立例項物件並工作,而完成這一個載入工作的角色就是ClassLoader。 類載入器ClassLoader介紹 Android的Dalvik/ART虛擬

Spring原始碼解析BeanFactoryPostProcessor

上一章,我們介紹了在AnnotationConfigApplicationContext初始化的時候,會建立AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner兩個物件: public class AnnotationConfigAppli

深入學習mysql表的操作

uniq order fault change incr 相關 約束 設置 type 1、表:是數據庫中的存儲數據的基本單位,一個表包含若幹個字段和值 2、創建表:   CREATE TABLE 表名稱 (   字段名1  數據庫類型1  [約束條件1],   字段名2  

自動化運維saltstackstates介紹及使用

配置文件 如何 states master 根目錄 一、什麽是Salt States?Salt States是Salt模塊的擴展,主系統使用的狀態系統叫SLS系統,SLS代表Saltstack State,Salt是一些狀態文件,其中包含有關如何配置Salt子節點的信息,這些狀態被存放在一