亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

pagehelper分頁亂套問題如何解決

發布時間:2022-11-29 09:58:40 來源:億速云 閱讀:178 作者:iii 欄目:開發技術

本篇內容介紹了“pagehelper分頁亂套問題如何解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    使用pagehelper遇到的坑說明

    現象是這樣的:我們有一個場景是查詢數據庫表中的全量記錄返回給第三方,但是突然某一天發現第三方告警說我們給的數據不對了,比如之前會給到200條記錄的,某次只給到了10條記錄。

    隨后我們推出了幾個猜想:

    1. 第三方系統處理數據有bug,漏掉了一些數據;

    2. 數據庫被人臨時改掉過,然后又被復原了;

    3. 數據庫bug,在全量select時可能不返回全部記錄;

    其實以上猜想都顯得有點無厘頭,比如數據庫怎么可能有這種低級bug?但是人在沒有辦法的情況下只能胡猜一通了。最后終于發現是pagehelper的原因,因為分頁亂套了,復用了其他場景下的分頁設置,丟到數據庫查詢后返回了10條記錄;

    pagehelper的至簡使用方式

    本身pagehelper就是一個輔助工具類,所以使用起來一般很簡單。尤其在springboot中,只要引用starter類,依賴就可以滿足了。(如果是其他版本,則可能需要配置下mybatis的intercepter)

    <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>${pagehelper.version}</version>
            </dependency>

    在使用時只需要加上 Page.startPage(pageNum, pageSize) 即可。

    public Object getUsers(int pageNum, int pageSize) {
            PageHelper.startPage(pageNum, pageSize);
            List<UserEntity> list = userMapper.selectAllWithPage(null);
            com.github.pagehelper.Page listWithPage = (com.github.pagehelper.Page) list;
            System.out.println("listCnt:" + listWithPage.getTotal());
            return list;
        }

    而真正的sql里只需按沒有分頁的樣式寫一下就可以了。

    <select id="selectAllWithPage" parameterType="java.util.Map"
            resultType="com.my.mvc.app.dao.entity.UserEntity">
            select * from t_users
        </select>

    還是很易用的。少去了一些寫死的sql樣例。

    pagehelper實現原理簡說

    pagehelper不是什么高深的組件,實際上它就是一個mybatis的一個插件或者攔截器。是mybatis在執行調用時,將請求轉發給pagehelper處理,然后由pagehelper包裝分頁邏輯。

    // com.github.pagehelper.PageInterceptor#intercept
        @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];
                }
                checkDialectExists();
                List resultList;
                //調用方法判斷是否需要進行分頁,如果不需要,直接返回結果
                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);
                }
                return dialect.afterPage(resultList, parameter, rowBounds);
            } finally {
                if(dialect != null){
                    dialect.afterAll();
                }
            }
        }

    如果沒有分頁邏輯需要處理,和普通的沒什么差別,如果有分頁請求,則會在原來的sql之上套上limit.. offset.. 之類的關鍵詞。從而完成分頁效果。

    為什么pagehelper的分頁會亂套?

    現在我們來說說為什么分頁會亂套?原因是 PageHelper.startPage(xx) 的原理是將分頁信息設置到線程上下文中,然后在隨后的查詢中使用該值,使用完成后就將該信息清除。

    /**
         * 開始分頁
         *
         * @param pageNum  頁碼
         * @param pageSize 每頁顯示數量
         * @param count    是否進行count查詢
         */
        public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
            return startPage(pageNum, pageSize, count, null, null);
        }
        /**
         * 開始分頁
         *
         * @param pageNum      頁碼
         * @param pageSize     每頁顯示數量
         * @param count        是否進行count查詢
         * @param reasonable   分頁合理化,null時用默認配置
         * @param pageSizeZero true且pageSize=0時返回全部結果,false時分頁,null時用默認配置
         */
        public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
            Page<E> page = new Page<E>(pageNum, pageSize, count);
            page.setReasonable(reasonable);
            page.setPageSizeZero(pageSizeZero);
            //當已經執行過orderBy的時候
            Page<E> oldPage = getLocalPage();
            if (oldPage != null && oldPage.isOrderByOnly()) {
                page.setOrderBy(oldPage.getOrderBy());
            }
            setLocalPage(page);
            return page;
        }
        protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
        /**
         * 設置 Page 參數
         *
         * @param page
         */
        protected static void setLocalPage(Page page) {
            LOCAL_PAGE.set(page);
        }
        // com.github.pagehelper.PageHelper#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();
        }

    那么什么情況下會導致分頁信息亂套呢?實際上就是線程變量什么情況會被亂用呢?

    線程被復用的時候,將可能導致該問題。比如某個請求將某個線程設置了一個線程變量,然后隨后另一個請求復用了該線程,那么這個變量就被復用過去了。那么什么情況下線程會被復用呢?

    一般是線程池、連接池等等。是的,大概就是這么原理了。

    分頁問題復現

    既然從理論上說明了這個問題,能否穩定復現呢?咱們編寫下面的,很快就復現了。

    @RestController
    @RequestMapping("/hello")
    @Slf4j
    public class HelloController {
        @Resource
        private UserService userService;
        // 1. 先請求該getUsers接口,將得到異常,pageNum=1, pageSize=1
        @GetMapping("getUsers")
        @ResponseBody
        public Object getUsers(int pageNum, int pageSize) {
            return userService.getUsers(pageNum, pageSize);
        }
        // 2. 多次請求該 getAllActors接口,正常情況下會得到N條全表記錄,但將會偶發地得到只有一條記錄,現象復現
        @GetMapping("getAllActors")
        @ResponseBody
        public Object getAllActors() {
            return userService.getAllActors();
        }
    }
    @Service
    @Slf4j
    public class UserService {
        @Resource
        private UserMapper userMapper;
        public Object getUsers(int pageNum, int pageSize) {
            PageHelper.startPage(pageNum, pageSize);
            // 此處強行拋出異常, 使以上 pagehelper 信息得以保存
            throw new RuntimeException("exception ran");
        }
        public Object getAllActors() {
            // 正常的全表查詢
            List<ActorEntity> list = userMapper.selectAllActors();
            return list;
        }
    }

    驗證步驟及結果如下:(數據方面,自己隨便找一些表就好了)

    // 步驟1: 發送請求: http://localhost:8081/hello/getUsers?pageNum=1&pageSize=1
    // 步驟2: 發送請求: http://localhost:8081/hello/getAllActors
    // 正常時返回
    [{"actorId":1,"firstName":"PENELOPE","lastName":null,"lastUpdate":null},{"actorId":2,"firstName":"NICK","lastName":null,"lastUpdate":null},{"actorId":3,"firstName":"ED","lastName":null,"lastUpdate":null},{"actorId":4,"firstName":"JENNIFER","lastName":null,"lastUpdate":null},{"actorId":5,"firstName":"JOHNNY","lastName":null,"lastUpdate":null},{"actorId":6,"firstName":"BETTE","lastName":null,"lastUpdate":null},{"actorId":7,"firstName":"GRACE","lastName":null,"lastUpdate":null},{"actorId":8,"firstName":"MATTHEW","lastName":null,"lastUpdate":null},{"actorId":9,"firstName":"JOE","lastName":null,"lastUpdate":null},{"actorId":10,"firstName":"CHRISTIAN","lastName":null,"lastUpdate":null},{"actorId":11,"firstName":"ZERO","lastName":null,"lastUpdate":null},{"actorId":12,"firstName":"KARL","lastName":null,"lastUpdate":null},{"actorId":13,"firstName":"UMA","lastName":null,"lastUpdate":null},{"actorId":14,"firstName":"VIVIEN","lastName":null,"lastUpdate":null},{"actorId":15,"firstName":"CUBA","lastName":null,"lastUpdate":null},{"actorId":16,"firstName":"FRED","lastName":null,"lastUpdate":null},... 
    // 出異常時返回
    [{"actorId":1,"firstName":"PENELOPE","lastName":null,"lastUpdate":null}]

    “pagehelper分頁亂套問題如何解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    双柏县| 水城县| 鹤山市| 剑川县| 高邑县| 林芝县| 策勒县| 迁西县| 武义县| 台东市| 萨迦县| 保山市| 汉源县| 宝坻区| 富平县| 景泰县| 阳信县| 容城县| 农安县| 伊春市| 龙山县| 板桥市| 望都县| 新兴县| 来宾市| 德江县| 巴塘县| 腾冲县| 文山县| 炎陵县| 大渡口区| 大洼县| 伊春市| 馆陶县| 获嘉县| 通江县| 榕江县| 江山市| 综艺| 北流市| 辽源市|