Le bug de pagehelper qui est resté en ligne jusqu'à 4h30 du matin ?

Cet article a participé à l'événement "Newcomer Creation Ceremony" pour commencer ensemble la route de la création d'or.

Bonjour à tous, je suis Roast Duck :

    Le week-end dernier, je me suis connecté jusqu'à 4h30 du matin et j'ai pleuré, je ne m'attendais pas à ce que le problème soit si simple. J'ai encore été paresseux récemment, et je l'ai rangé depuis que je l'ai écrit au début, et je l'ai finalement inventé aujourd'hui.

insérez la description de l'image ici

journal des problèmes

Error querying database. Cause: com.github.pagehelper.PageException: 被分页的语句已经包含了Top,不能再通过分页插件进行分页查询!
复制代码

La chose étrange est que la déclaration d'erreur n'utilise pas le plugin de pagination, c'est juste une simple requête.

Conjecture de cause

Si vous rencontrez un problème, tout ira bien après la première restauration, indiquant que le problème se produit dans cette soumission.

Relatif à pagehelper.

  • Modifiez le fichier pom, changez les dépendances liées à pagehelper et causez des problèmes. Pas vraiment.
  • Modifier la configuration de pagehelper (classe de configuration ou yml). ni.
  • La méthode d'exclusion consiste à aller partiellement en ligne pour le code soumis cette fois.

Récurrence du problème

Au final, j'ai trouvé qu'il y a bien un endroit où c'est écrit comme ça.

En raison de la scission du projet, la requête de base de données directe d'origine a été remplacée par des appels http, mais la personne qui a réécrit n'a pas prêté attention à la logique métier, ce qui n'a entraîné aucun commentaire dans le code de pagination. (Bien que le commentaire soit erroné, la méthode de réécriture http ne prend pas en charge les paramètres de pagination)

PageHelper.startPage(pageNum,pageSize);
//注释dao,改为http调用
//xxxDao.selectXxx();
xxxHttp.select();
复制代码

De cette façon, la pagination n'est pas utilisée dans le thread en cours. Lorsque ce thread exécute d'autres requêtes SQL, il y aura des problèmes. Voir l'analyse du code source ci-dessous pour plus de détails.

Analyse du code source

Notre base de données est SqlServer, et les implémentations de pagination de Mysql et SqlServer sont différentes.

Les projets de démonstration suivants simulent différents scénarios.

Examinez principalement la pagination et le code source de l'implémentation de SqlServer.

L'intégralité du code de base du plugin de pagination se trouve dans l'intercepteur PageInterceptor.

Lorsque PageHelper.startPage(pageNum,pageSize); est exécuté, les paramètres de pagination seront placés dans TheadLocal.

PageMéthode

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;
}
复制代码

Le code AfterAll() exécuté dans finally est d'effacer ThreadLocal après l'exécution

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();
        }
    }
}
复制代码

Reproduisez le message d'erreur SqlServer :

insérez la description de l'image ici

La requête MySQL signalera également une erreur de limite

insérez la description de l'image ici

SqlServerParser, la déclaration d'erreur apparaît ici

insérez la description de l'image ici

Selon le code source, une telle déclaration sera générée en premier, puis remplacée en fonction du nombre et de la taille entrants

SELECT TOP 9223372036854775807 user_no FROM (SELECT ROW_NUMBER() OVER (ORDER BY RAND()) PAGE_ROW_NUMBER, user_no FROM (SELECT user_no FROM dbo.[user]) AS PAGE_TABLE_ALIAS) AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > -9223372036854775808 ORDER BY PAGE_ROW_NUMBER
复制代码

insérez la description de l'image ici

Le code source ci-dessus est pour la pagination SqlServer, en fait, il suffit d'atteindre un point d'arrêt et de le suivre.

Adresse du projet de démonstration

gitee.com/fireduck_ad…

Résumer

L'une des principales raisons pour lesquelles ce problème a été renvoyé à 4 heures et que l'environnement de test et l'environnement local n'ont pas été reproduits, l'une des principales raisons est que le trafic n'est pas suffisant. Peut-être qu'un ou deux messages d'erreur seront ignoré, ce qui complique la résolution du problème.

De plus, ce n'est pas l'objet de test de régression, et la vérification du code qui est lancé sera renforcée à l'avenir.

Je suppose que tu aimes

Origine juejin.im/post/7083457707566432270
conseillé
Classement