Guide d'Intégration et d'Optimisation de MyBatis en Java

Architecture de Persistance et ORM

Les frameworks ORM (Object-Relational Mapping) agissent comme une passerelle entre le paradigme orienté objet des applications et le modèle relationnel des bases de données. Contrairement à l'API JDBC standard, qui impose la gestion manuelle des connexions, la préparation des instructions et l'extraction fastidieuse des jeux de résultats, un ORM automatise la synchronisation entre les instances d'objets en mémoire et les enregistrements de la base de données.

MyBatis se distingue en tant que framework de persistance semi-automatique. Il élimine le code répétitif de JDBC tout en déléguant le contrôle total des instructions SQL au développeur, évitant ainsi les pièges de génération de requêtes opaques propres aux ORM complets.

Initialisation de l'Environnement Maven

La mise en place d'un projet MyBatis nécessite l'inclusion des bibliothèques fondamentales pour le moteur de persistance, le pilote de base de données, la journalisation et les tests unitaires.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>fr.dev.persistence</groupId>
    <artifactId>mybatis-core</artifactId>
    <version>2.0.0</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

Fichiers de Configuration Centraux

Le fichier mybatis-config.xml orchestre l'environnement d'exécution, les connexions et l'enregistrement des mappers.

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
    <properties resource="database.properties"/>
    
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <package name="fr.dev.persistence.modeles"/>
    </typeAliases>

    <environments default="production">
        <environment id="production">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.user}"/>
                <property name="password" value="${db.pass}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="fr.dev.persistence.mappers"/>
    </mappers>
</configuration>

Le fichier database.properties isole les variables sensibles et spécifiques à l'environnement :

db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://127.0.0.1:3306/app_db?useSSL=false&serverTimezone=UTC
db.user=admin_sys
db.pass=S3cur3P@ssw0rd

Cycle de Développement : CRUD

La modélisation commence par la définition du schéma relationnel, suivi de l'entité Java, de l'interface du repository et du document XML de mapping.

Modèle et Interface

CREATE TABLE comptes (
    id_compte INT AUTO_INCREMENT PRIMARY KEY,
    nom_affichage VARCHAR(100),
    hash_mdp VARCHAR(255),
    est_actif BOOLEAN,
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
package fr.dev.persistence.modeles;

import java.time.LocalDateTime;

public class Compte {
    private Integer idCompte;
    private String nomAffichage;
    private String hashMdp;
    private Boolean estActif;
    private LocalDateTime dateCreation;
    
    // Constructeurs, Getters et Setters omis pour concision
}
package fr.dev.persistence.repositories;

import fr.dev.persistence.modeles.Compte;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface CompteRepository {
    Compte recupererParId(@Param("id") Integer id);
    List<Compte> rechercherParNom(@Param("fragmentNom") String nom);
    int creerCompte(Compte compte);
}

Définition du Mapper XML

<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="fr.dev.persistence.repositories.CompteRepository">

    <select id="recupererParId" resultType="Compte">
        SELECT id_compte AS idCompte, nom_affichage AS nomAffichage, 
               hash_mdp AS hashMdp, est_actif AS estActif, date_creation AS dateCreation
        FROM comptes
        WHERE id_compte = #{id}
    </select>

    <select id="rechercherParNom" resultType="Compte">
        SELECT * FROM comptes
        WHERE nom_affichage LIKE CONCAT('%', #{fragmentNom}, '%')
    </select>

    <insert id="creerCompte" useGeneratedKeys="true" keyProperty="idCompte">
        INSERT INTO comptes (nom_affichage, hash_mdp, est_actif)
        VALUES (#{nomAffichage}, #{hashMdp}, #{estActif})
    </insert>

</mapper>

Encapsulation du Gestionnaire de Sessions

Pour éviter la prolifération de code d'initialisation et garantir la sécurité des threads, une classe utilitaire basée sur ThreadLocal est recommandée.

package fr.dev.persistence.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;

public class GestionnairePersistance {
    private static final SqlSessionFactory fabrique;
    private static final ThreadLocal<SqlSession> contexteLocal = new ThreadLocal<>();

    static {
        try {
            fabrique = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new ExceptionInInitializerError("Échec de l'initialisation de MyBatis", e);
        }
    }

    public static SqlSession obtenirSession() {
        SqlSession session = contexteLocal.get();
        if (session == null) {
            session = fabrique.openSession();
            contexteLocal.set(session);
        }
        return session;
    }

    public static <T> T obtenirMapper(Class<T> classeMapper) {
        return obtenirSession().getMapper(classeMapper);
    }

    public static void finaliserTransaction(boolean succes) {
        SqlSession session = contexteLocal.get();
        if (session != null) {
            try {
                if (succes) session.commit();
                else session.rollback();
            } finally {
                session.close();
                contexteLocal.remove();
            }
        }
    }
}

Résolution des Mappings Complexes

Lorsque les conventions de nommage diffèrent entre la base de données et le code source, ou lors de jointures, l'élément <resultMap> offre une précision totale.

<resultMap id="mappingCompteDetaille" type="Compte">
    <id column="id_compte" property="idCompte"/>
    <result column="nom_affichage" property="nomAffichage"/>
    <result column="date_creation" property="dateCreation"/>
</resultMap>

Relations entre Entités

MyBatis gère les associations via des requêtes jointes mappées correctement.

  • Un-à-Un : Utilisatoin de la balise <association> pour lier un objet unique.
  • Un-à-Plusieurs : Utilisation de <collection> pour hydrater des structures de type List.
  • Plusieurs-à-Plusieurs : Nécessite une table pivot et l'utilisation de <collection> des deux côtés de la relation.
<resultMap id="mappingCategorie" type="Categorie">
    <id property="idCategorie" column="cat_id"/>
    <result property="libelle" column="cat_nom"/>
    <!-- Relation Un-à-Plusieurs -->
    <collection property="produits" ofType="Produit">
        <id property="idProduit" column="prod_id"/>
        <result property="designation" column="prod_nom"/>
    </collection>
</resultMap>

Construction de Requêtes Dynamiques

Les balises dynamiques adaptent la syntaxe SQL en fonction des paramètres d'entrée fournis à l'exécution.

Filtrage Conditionnel avec <where> et <set>

<update id="mettreAJourCompte">
    UPDATE comptes
    <set>
        <if test="nomAffichage != null">nom_affichage = #{nomAffichage},</if>
        <if test="hashMdp != null">hash_mdp = #{hashMdp},</if>
        <if test="estActif != null">est_actif = #{estActif}</if>
    </set>
    WHERE id_compte = #{idCompte}
</update>

Traitements par Lots avec <foreach>

<insert id="insertionGroupee">
    INSERT INTO journaux_evenements (code_event, description) VALUES
    <foreach collection="listeEvenements" item="evt" separator=",">
        (#{evt.code}, #{evt.description})
    </foreach>
</insert>

Stratégies de Mise en Cache

MyBatis intègre nativement deux niveaux de cache pour réduire la charge sur le serveur de base de données.

  • Cache Local (Niveau 1) : Associé à la session (SqlSession). Il est activé par défaut et partage les données au sein d'une même transaction.
  • Cache Global (Niveau 2) : Associé à l'espace de noms du Mapper. Il nécessite une activation explicite et survit à la fermeture des sessions.
<mapper namespace="fr.dev.persistence.repositories.CompteRepository">
    <!-- Activation du cache de second niveau pour ce mapper -->
    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    
    <select id="recupererParId" resultType="Compte" useCache="true">
        SELECT * FROM comptes WHERE id_compte = #{id}
    </select>
</mapper>

Intégration d'Écosystèmes Tiers

Pool de Connexions avec Druid

Pour les applications à fort trafic, remplacer le pool natif par Alibaba Druid offre des capacités de surveillance et de robustesse accrues.

package fr.dev.persistence.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

public class FabriqueDruidPersonnalisee extends UnpooledDataSourceFactory {
    public FabriqueDruidPersonnalisee() {
        DruidDataSource source = new DruidDataSource();
        source.setInitialSize(5);
        source.setMaxActive(20);
        source.setTestWhileIdle(true);
        this.dataSource = source;
    }
}

Cette fabrique est ensuite référencée dans la configuration XML via l'attribut type de la balise <dataSource>.

Pagination Physique avec PageHelper

PageHelper intercepte les requêtes pour y injecter dynamiquement les clauses LIMIT et OFFSET spécifiques au dialecte SQL utilisé.

<!-- Ajout dans mybatis-config.xml -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import java.util.List;

public class ServiceCompte {
    public PageInfo<Compte> listerComptes(int page, int taille) {
        PageHelper.startPage(page, taille);
        List<Compte> resultats = GestionnairePersistance
            .obtenirMapper(CompteRepository.class)
            .recupererTous();
            
        return new PageInfo<>(resultats);
    }
}

Étiquettes: MyBatis Java ORM JDBC SQL-Dynamique

Publié le 21 juin à 00h48