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
RowBoundsouPageRowBounds. - Utilisation de l'interface
ISelectpour 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.