Comprendre le framework MyBatis
MyBatis se positionne comme un framework de mapping objet-relationnel (ORM) partiel. Contrairement aux solutions ORM complètes, il encapsule les opérations JDBC tout en laissant aux développeurs le contrôle total sur les requêtes SQL. Cette approche élimine les tâches répétitives telles que la configuration des pilotes et la gestion des connexions, permettant une concentration sur l'écriture de SQL natif. La configuration et le mapping s'effectuent via des fichiers XML ou des annotations, transformant les objets POJO en enregistrements de base de données sans générer de code JDBC manuel.
Avantages et inconvénients
Le framework offre une flexibilité remarquable grâce à la séparation des requêtes SQL dans des fichiers XML, ce qui facilite leur maintenance et leur modification. Il réduit considérablement le volume de code par rapport aux implémentations JDBC pures et maintient une excellente compatibilité avec divers systèmes de bases de données supportés par JDBC. L'intégration avec Spring est également simple.
Cependant, la rédaction de SQL peut devenir fastidieuse pour les schémas complexes, et les requêtes deviennent dépendantes d'un système de base de données spécifique, limitant la portabilité.
Utilisation recommandée
MyBatis est particulièrement adapté aux projets exigeant une optimisation fine des performances et une adaptabilité aux changements fréquents, comme les applications web modernes. Il constitue une solution élégante pour la couche d'accès aux données lorsque le contrôle du SQL est primordial.
Distinguons MyBatis d'Hibernate
La distinction fondamentale réside dans leur philosophie. MyBatis requiert une écriture manuelle du SQL, offrant ainsi un contrôle précis sur l'exécution des requêtes. Cette flexibilité est idéale pour les projets où les exigences évoluent rapidement. En contrepartie, le support multi-bases de données nécessite la création de fichiers de mapping spécifiques. Hibernate, en revanche, automatise davantage le mapping ORM, assurant une indépendance par rapport à la base de données au détriment d'un certain niveau de contrôle.
Gestion des paramètres et sécurité
La syntaxe #{} dans MyBatis déclenche un pré-traitement similaire aux requêtes préparées JDBC, remplaçant les paramètres par des points d'interrogation (?). Cette méthode protège contre les injections SQL. À l'inverse, la syntaxe ${} effectue une substitution directe de chaînes, ce qui peut présenter des risques de sécurité si elle est mal utilisée.
Correspondance des noms de champs
Lorsque les attributs d'une classe Java diffèrent des colonnes de la table, deux stratégies s'offrent à nous. La première consiste à utiliser des alias dans la requête SQL pour harmoniser les noms. La seconde repose sur l'utilisation d'un <resultMap> pour définir explicitement la correspondance entre chaque colonne et son attribut correspondant.
<select id="obtenirCommande" parametreType="int" resultatType="Commande">
SELECT commande_id AS id, numero_commande AS numero, prix_commande AS prix
FROM commandes WHERE commande_id = #{id};
</select>
<resultMap type="Commande" id="mapCommande">
<id property="id" colonne="commande_id"/>
<result property="numero" colonne="numero_commande"/>
<result property="prix" colonne="prix_commande"/>
</resultMap>
Requêtes de recherche partielle
Les recherches avec filtre "like" peuvent être implémentées de deux manières. La méthode sûre consiste à injecter les caractères génériques (%) directement dans la valeur du paramètre côté Java. L'alternative, qui consiste à concaténer les caractères dans la requête SQL, expose à des vulnérabilités d'injection.
// Méthode sûre
String motif = "%terme%";
List<Resultat> resultats = mapper.rechercherParMotif(motif);
<select id="rechercherParMotif">
SELECT * FROM articles WHERE description LIKE #{motif}
</select>
Principe des interfaces Mapper
Chaque interface Mapper est liée à un fichier de mapping XML. Son espace de noms (namespace) doit correspondre au nom complet de l'interface. Les méthodes de l'interface correspondent aux identifiants des instructions SQL (statement ID). MyBatis génère dynamiquement un proxy pour ces itnerfaces via la réflexion JDK, redirigeant les appels vers les instructions SQL associées. Notez que la surcharge des méthodes n'est pas supportée car l'identification repose sur la combinaison du nom complet de l'interface et du nom de la méthode.
Techniques de pagination
MyBatis propose un mécanisme de pagination en mémoire via l'objet RowBounds. Pour une pagination physique, il est recommandé d'implémenter un plugin d'interception. Ce plugin modifie dynamiquement la requête SQL pour y ajouter des clauses de limitation spécifiques au dialecte de la base de données utilisée.
Mapping des résultats
Le framework mapte les résultats des requêtes vers des objets Java en utilisant soit des <resultMap> explicites, soit des alias de colonnes correspondant aux noms des attributs. Le processus implique la création d'instances par réflexion et l'assignation des valeurs via des accesseurs.
Insertion par lots et clés générées
Une insertion par lots efficace s'obtient en utilisant un SqlSession configuré avec l'ExecutorType.BATCH. Pour récupérer les clés primaires auto-générées après insertion, il faut activer l'attribut useGeneratedKeys="true" dans la balise <insert> et spécifier la propriété cible via keyProperty.
<insert id="insererArticle" useGeneratedKeys="true" keyProperty="id">
INSERT INTO articles (titre, contenu) VALUES (#{titre}, #{contenu})
</insert>
Article nouvelArticle = new Article();
nouvelArticle.setTitre("Nouvel Article");
mapper.insererArticle(nouvelArticle);
long idGenere = nouvelArticle.getId();
Passage de paramètres multiples
Pour transmettre plusieurs paramètres à une méthode de Mapper, trois approches existent. On peut référencer les paramètres par leur position (ex: #{0}, #{1}), utiliser l'annotation @Param pour leur donner un nom symbolique, ou les regrouper dans une Map.
SQL dynamique
Les balises dynamiques telles que <if>, <choose>, <where>, <set> et <foreach> permettent de construire des requêtes SQL conditionnelles et flexibles directement dans les fichiers XML de mapping.
Caches et performances
Le cache de premier niveau est local à la session et activé par défaut. Le cache de second niveau est partagé entre toutes les sessions d'un même Mapper et doit être activé explicitement. Les opérations d'écriture (insert, update, delete) entraînent l'invalidaiton du cache concerné pour garantir la cohérence des données.
Relations et associations
Les relations de type un-à-un et un-à-plusieurs sont gérées via les balises <association> et <collection> dans les <resultMap>. Le chargement peut être immédiat (jointure) ou différé (requête séparée), ce dernier activant l'exécution de la requête liée uniquement lors de l'accès à la propriété correspondante.
Intégration et extension
L'intégration avec Spring peut se faire via des MapperFactoryBean ou un scanner automatique des interfaces Mapper. MyBatis offre également une API pour créer des plugins interceptant les opérations sur les handlers d'exécution, de paramètres et de résultats.