欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

pagehelper踩坑記之分頁(yè)亂套問(wèn)題解決

 更新時(shí)間:2022年11月28日 17:10:20   作者:等你歸去來(lái)  
這篇文章主要為大家介紹了pagehelper踩坑記之分頁(yè)亂套問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

我們?cè)谑褂脭?shù)據(jù)庫(kù)進(jìn)行查詢時(shí),很多時(shí)候會(huì)用到分頁(yè)展示功能,因此除了像mybatis這樣的完善的orm框架之外,還有pagehelper這樣的插件幫助減輕我們的工作。

pagehelper的實(shí)現(xiàn)方式是,不需要我們?nèi)ゾ帉懛猪?yè)代碼,只需要調(diào)用一個(gè)分頁(yè)方法,出來(lái)的結(jié)果就是經(jīng)過(guò)分頁(yè)處理的。一來(lái),我們的xml中的sql編寫就會(huì)靈活很多,二來(lái),它可以幫我們規(guī)避各種不同類型的數(shù)據(jù)庫(kù)的分頁(yè)描述方式。所以,總體來(lái)說(shuō)是個(gè)好事。

使用pagehelper遇到的坑說(shuō)明

現(xiàn)象是這樣的:我們有一個(gè)場(chǎng)景是查詢數(shù)據(jù)庫(kù)表中的全量記錄返回給第三方,但是突然某一天發(fā)現(xiàn)第三方告警說(shuō)我們給的數(shù)據(jù)不對(duì)了,比如之前會(huì)給到200條記錄的,某次只給到了10條記錄。

隨后我們推出了幾個(gè)猜想:

1. 第三方系統(tǒng)處理數(shù)據(jù)有bug,漏掉了一些數(shù)據(jù);

2. 數(shù)據(jù)庫(kù)被人臨時(shí)改掉過(guò),然后又被復(fù)原了;

3. 數(shù)據(jù)庫(kù)bug,在全量select時(shí)可能不返回全部記錄;

其實(shí)以上猜想都顯得有點(diǎn)無(wú)厘頭,比如數(shù)據(jù)庫(kù)怎么可能有這種低級(jí)bug?但是人在沒(méi)有辦法的情況下只能胡猜一通了。最后終于發(fā)現(xiàn)是pagehelper的原因,因?yàn)榉猪?yè)亂套了,復(fù)用了其他場(chǎng)景下的分頁(yè)設(shè)置,丟到數(shù)據(jù)庫(kù)查詢后返回了10條記錄;

pagehelper的至簡(jiǎn)使用方式

本身pagehelper就是一個(gè)輔助工具類,所以使用起來(lái)一般很簡(jiǎn)單。尤其在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>

在使用時(shí)只需要加上 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里只需按沒(méi)有分頁(yè)的樣式寫一下就可以了。

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

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

pagehelper實(shí)現(xiàn)原理簡(jiǎn)說(shuō)

pagehelper不是什么高深的組件,實(shí)際上它就是一個(gè)mybatis的一個(gè)插件或者攔截器。是mybatis在執(zhí)行調(diào)用時(shí),將請(qǐng)求轉(zhuǎn)發(fā)給pagehelper處理,然后由pagehelper包裝分頁(yè)邏輯。

// 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;
            //由于邏輯關(guān)系,只會(huì)進(jìn)入一次
            if (args.length == 4) {
                //4 個(gè)參數(shù)時(shí)
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 個(gè)參數(shù)時(shí)
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            checkDialectExists();
            List resultList;
            //調(diào)用方法判斷是否需要進(jìn)行分頁(yè),如果不需要,直接返回結(jié)果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //判斷是否需要進(jìn)行 count 查詢
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    //查詢總數(shù)
                    Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    //處理查詢總數(shù),返回 true 時(shí)繼續(xù)分頁(yè)查詢,false 時(shí)直接返回
                    if (!dialect.afterCount(count, parameter, rowBounds)) {
                        //當(dāng)查詢總數(shù)為 0 時(shí),直接返回空的結(jié)果
                        return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    }
                }
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                //rowBounds用參數(shù)值,不使用分頁(yè)插件處理時(shí),仍然支持默認(rèn)的內(nèi)存分頁(yè)
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if(dialect != null){
                dialect.afterAll();
            }
        }
    }

如果沒(méi)有分頁(yè)邏輯需要處理,和普通的沒(méi)什么差別,如果有分頁(yè)請(qǐng)求,則會(huì)在原來(lái)的sql之上套上limit.. offset.. 之類的關(guān)鍵詞。從而完成分頁(yè)效果。

為什么pagehelper的分頁(yè)會(huì)亂套?

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

/**
     * 開始分頁(yè)
     *
     * @param pageNum  頁(yè)碼
     * @param pageSize 每頁(yè)顯示數(shù)量
     * @param count    是否進(jìn)行count查詢
     */
    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
        return startPage(pageNum, pageSize, count, null, null);
    }
    /**
     * 開始分頁(yè)
     *
     * @param pageNum      頁(yè)碼
     * @param pageSize     每頁(yè)顯示數(shù)量
     * @param count        是否進(jìn)行count查詢
     * @param reasonable   分頁(yè)合理化,null時(shí)用默認(rèn)配置
     * @param pageSizeZero true且pageSize=0時(shí)返回全部結(jié)果,false時(shí)分頁(yè),null時(shí)用默認(rèn)配置
     */
    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);
        //當(dāng)已經(jīng)執(zhí)行過(guò)orderBy的時(shí)候
        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>();
    /**
     * 設(shè)置 Page 參數(shù)
     *
     * @param page
     */
    protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }
    // com.github.pagehelper.PageHelper#afterAll
    @Override
    public void afterAll() {
        //這個(gè)方法即使不分頁(yè)也會(huì)被執(zhí)行,所以要判斷 null
        AbstractHelperDialect delegate = autoDialect.getDelegate();
        if (delegate != null) {
            delegate.afterAll();
            autoDialect.clearDelegate();
        }
        clearPage();
    }
    /**
     * 移除本地變量
     */
    public static void clearPage() {
        LOCAL_PAGE.remove();
    }

那么什么情況下會(huì)導(dǎo)致分頁(yè)信息亂套呢?實(shí)際上就是線程變量什么情況會(huì)被亂用呢?

線程被復(fù)用的時(shí)候,將可能導(dǎo)致該問(wèn)題。比如某個(gè)請(qǐng)求將某個(gè)線程設(shè)置了一個(gè)線程變量,然后隨后另一個(gè)請(qǐng)求復(fù)用了該線程,那么這個(gè)變量就被復(fù)用過(guò)去了。那么什么情況下線程會(huì)被復(fù)用呢?

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

分頁(yè)問(wèn)題復(fù)現(xiàn)

既然從理論上說(shuō)明了這個(gè)問(wèn)題,能否穩(wěn)定復(fù)現(xiàn)呢?咱們編寫下面的,很快就復(fù)現(xiàn)了。

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

驗(yàn)證步驟及結(jié)果如下:(數(shù)據(jù)方面,自己隨便找一些表就好了)

// 步驟1: 發(fā)送請(qǐng)求: http://localhost:8081/hello/getUsers?pageNum=1&pageSize=1
// 步驟2: 發(fā)送請(qǐng)求: http://localhost:8081/hello/getAllActors
// 正常時(shí)返回
[{"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},... 
// 出異常時(shí)返回
[{"actorId":1,"firstName":"PENELOPE","lastName":null,"lastUpdate":null}]

以上,幾乎都可以復(fù)現(xiàn)該現(xiàn)象。實(shí)際上該問(wèn)題由于tomcat的連接池復(fù)用導(dǎo)致的,本身和pagehelper關(guān)聯(lián)不是很大,但是在此處卻可能帶來(lái)比較大的影響。這也警示我們使用ThreadLocal 時(shí),一定要小心清理,否則將產(chǎn)生難以預(yù)料的結(jié)果。而且將很難排查。供諸君參考,更多關(guān)于pagehelper分頁(yè)亂套解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringCloud 服務(wù)網(wǎng)關(guān)路由規(guī)則的坑及解決

    SpringCloud 服務(wù)網(wǎng)關(guān)路由規(guī)則的坑及解決

    這篇文章主要介紹了SpringCloud 服務(wù)網(wǎng)關(guān)路由規(guī)則的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java多線程下載網(wǎng)圖的完整案例

    Java多線程下載網(wǎng)圖的完整案例

    這篇文章主要給大家介紹了關(guān)于Java多線程下載網(wǎng)圖的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • 簡(jiǎn)單談?wù)刯ava中final,finally,finalize的區(qū)別

    簡(jiǎn)單談?wù)刯ava中final,finally,finalize的區(qū)別

    Java中final、finally、finalize的區(qū)別與用法,困擾了不少學(xué)習(xí)者,下面我們就這個(gè)問(wèn)題進(jìn)行一些探討,希望對(duì)大家的學(xué)習(xí)有所幫助。
    2016-05-05
  • Spring Security保護(hù)用戶密碼常用方法詳解

    Spring Security保護(hù)用戶密碼常用方法詳解

    這篇文章主要介紹了Spring Security保護(hù)用戶密碼常用方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • SpringBoot實(shí)現(xiàn)接口文檔自動(dòng)生成的方法示例

    SpringBoot實(shí)現(xiàn)接口文檔自動(dòng)生成的方法示例

    在開發(fā)Web應(yīng)用程序時(shí),接口文檔是非常重要的一環(huán),本文主要介紹了SpringBoot實(shí)現(xiàn)接口文檔自動(dòng)生成的方法示例,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • 詳解SpringBoot下文件上傳與下載的實(shí)現(xiàn)

    詳解SpringBoot下文件上傳與下載的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot下文件上傳與下載的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • springboot注解Aspect實(shí)現(xiàn)方案

    springboot注解Aspect實(shí)現(xiàn)方案

    本文提供一種自定義注解,來(lái)實(shí)現(xiàn)業(yè)務(wù)審批操作的DEMO,不包含審批流程的配置功能。對(duì)springboot注解Aspect實(shí)現(xiàn)方案感興趣的朋友一起看看吧
    2022-01-01
  • java+mysql實(shí)現(xiàn)登錄和注冊(cè)功能

    java+mysql實(shí)現(xiàn)登錄和注冊(cè)功能

    這篇文章主要為大家詳細(xì)介紹了java+mysql實(shí)現(xiàn)登錄和注冊(cè)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 使用SpringSecurity+defaultSuccessUrl不跳轉(zhuǎn)指定頁(yè)面的問(wèn)題解決方法

    使用SpringSecurity+defaultSuccessUrl不跳轉(zhuǎn)指定頁(yè)面的問(wèn)題解決方法

    本人是用springsecurity的新手,今天遇到defaultSuccessUrl不跳轉(zhuǎn)指定頁(yè)面的問(wèn)題,真是頭疼死了,網(wǎng)上找遍了解決方法都解決不了,今天給大家分享使用SpringSecurity+defaultSuccessUrl不跳轉(zhuǎn)指定頁(yè)面的問(wèn)題解決方法,感興趣的朋友一起看看吧
    2023-12-12
  • Java?Lambda表達(dá)式常用的函數(shù)式接口

    Java?Lambda表達(dá)式常用的函數(shù)式接口

    這篇文章主要介紹了Java?Lambda表達(dá)式常用的函數(shù)式接口,文章基于Java?Lambda表達(dá)式展開對(duì)常用的函數(shù)式接口的介紹,具有一的的參考價(jià)值需要的小伙伴可以參考一下
    2022-04-04

最新評(píng)論