Problème de pagination inattendue dans les requêtes SELECT avec MyBatis et PageHelper

Contexte

Durant l'exécution d'une application, une requête SELECT de liste normale a généré une erreur sporadique. En analysant les journaux, il a été constaté que la clause SQL était modifiée pour inclure ORDER BY create_time DESC LIMIT ? à la fin, sans que ce code soit explicitement écrit. Le problème a été attribué à un comportement du framework.

Analyse du problème

Les journaux d'erreur ont révélé une exception d'intégrité MySQL due à une ambiguïté de colonne dans la clause ORDER BY :


MySQLIntegrityConstraintViolationException: Column 'create_time' in order clause is ambiguous

La trace de la pile d'exécution a montré que l'intercepteur PageHelper de MyBatis était impliqué. Une investigation plus poussée a indiqué que les paramètres de pagination stockés dans un ThreadLocal n'avaient pas été nettoyés correctemnet, entraînant leur réutilisation accidentelle lors de requêtes ultérieures sur le même thread.

Solution

Le problème a été identifié dans des sections du code où PageHelper.startPage() était appelé, mais la requête MyBatis correspondante n'était pas exécutée immédiatement (par exemple, en raison de conditions ou de retours prématurés). Cela laissait les paramètres de pagination dans le ThreadLocal, affectant des requêtes futures non liées. La résolution consiste à garantir que l'appel de démarrage de pagination est toujours suivi d'une exécution de requête, ou à utilisre des méthodes sûres de PageHelper.

Bonnes pratiques avec PageHelper

Selon la documentation officielle de PageHelper, plusieurs approches garantissent une utilisation sûre :

  • Utilisation des paramètres RowBounds ou PageRowBounds.
  • Utilisation de l'interface ISelect pour les requêtes.
  • Assurer que PageHelper.startPage() est immédiatement suivi d'une requête MyBatis dans le même flux d'exécution.

Un exemple d'utilisation non sûre :


PaginationUtil.beginRange(1, 10);
List<User> results;
if (filter != null) {
    results = userDao.queryByFilter(filter);
} else {
    results = Collections.emptyList();
}

Dans ce cas, si filter est nul, la requête n'est pas exécutée, et les paramètres de pagination restent dans le ThreadLocal. La version sûre déplace l'appel de pagination à l'intérieur de la condition :


List<User> results;
if (filter != null) {
    PaginationUtil.beginRange(1, 10);
    results = userDao.queryByFilter(filter);
} else {
    results = Collections.emptyList();
}

Pour une sécurité supplémentaire, un nettoyage manuel peut être effectué, bien que cela soit souvent redondant :


List<User> results;
if (filter != null) {
    PaginationUtil.beginRange(1, 10);
    try {
        results = userDao.fetchAll();
    } finally {
        PaginationUtil.clearPagination();
    }
} else {
    results = Collections.emptyList();
}

Ce pattern garantit que les ressources sont libérées même en cas d'exception.

Étiquettes: MyBatis PageHelper Java ThreadLocal SQL

Publié le 8 juin à 01h44