Guide complet sur le mapping de résultats avec ResultMap dans MyBatis

MyBatis repose sur l'idée que la structure de la base de données n'est pas toujours contrôlable. Bien que l'on souhaite respecter la troisième forme normale ou la BCNF, la réalité est souvent différente. Le mapping de résultats est le mécanisme offert par MyBatis pour gérer cette transition entre l'idéal et le réel, et la balise resultMap est l'outil central pour configurer ce mapping.

Avant d'explorer en détail la balise resultMap, il est essentiel de comprendre le processus de trnasformation d'un résultat de requête SQL en un objet Java (POJO ou JavaBean).

Cheminement d'un résultat SQL vers un objet métier :

  1. Obtention d'un objet ResultSet via JDBC.
  2. Parcours du ResultSet et stockage temporaire de chaque ligne dans une instance de HashMap, avec les noms de colonnes (ou alias) comme clés et les valeurs correspondantes comme valeurs.
  3. Instanciation de l'objet métier par réflexion, en utilisant l'attribut type de la balise resultMap.
  4. Remplissage de l'instance de l'objet métier à partir des paires clé-valeur de la HashMap, conformément aux informations contenues dans les sous-balises id et result du resultMap.

1. La balise resultMap

1.1 Attributs

  • id : Identifiant unique du resultMap.
  • type : Nom complet de la classe ou alias de type de l'objet à retourner.
  • autoMapping : Valeur possible true (par défaut) ou false. Active ou désactive le mappage automatique. Lorsqu'il est activé, MyBatis recherche automatiquement une propriété de l'objet dont le nom (en minuscules) correspond au nom de la colonne et appelle le setter correspondant. S'il est désactivé, vous devez explicitement déclarer chaque relation de mapping dans le resultMap.

1.2 Usage fondamental : Établir la correspondance entre les colonnes du résultat SQL et les propriétés d'un objet

Exemple 1 : Construction via les setters

public class Etudiant {
    private long id;
    private String nom;
    private int age;

    // Getters et setters

    // Constructeur sans argument obligatoire
    public Etudiant() {}
}

<select id="getEtudiant" resultMap="etudiantRM">
  SELECT ID, Nom, Age FROM TEtudiant
</select>

<resultMap id="etudiantRM" type="Etudiant">
  <id property="id" column="ID"/>
  <result property="nom" column="Nom"/>
  <result property="age" column="Age"/>
</resultMap>

Sous-balises :

  • id : Mappe la colonne de clé primaire à la propriété correspondante de l'objet.
  • result : Mappe une colonne standard à la propriété correspondante de l'objet.

Détails des attributs des balises id et result :

Attribut Description
property Nom de la propriété du JavaBean à mapper.
column Nom de la colonne (ou alias) dans la table.
javaType Nom complet de la classe ou alias. Généralement détecté automatiquement pour un JavaBean, mais nécessaire pour un HashMap.
jdbcType Type JDBC. Utile pour les colonnes pouvant être nulles lors d'opérations CUD. JDBC l'exige, MyBatis l'accepte.
typeHandler Remplace le gestionnaire de type par défaut, soit par un nom de classe complet, soit par un alias.

Exemple 2 : Construction via un constructeur avec paramètres

<select id="getEtudiant" resultMap="etudiantRM">
  SELECT ID, Nom, Age FROM TEtudiant
</select>

<resultMap id="etudiantRM" type="Etudiant">
  <constructor>
    <idArg column="ID" javaType="_long"/>
    <arg column="Nom" javaType="String"/>
    <arg column="Age" javaType="_int"/>
  </constructor>
</resultMap>

Sous-balises du constructeur :

  • constructor : Spécifie le constructeur à utiliser. L'ordre des sous-éléments doit correspondre à l'ordre des paramètres du constructeur.
  • idArg : Marque un paramètre comme étant la clé primaire.
  • arg : Marque un paramètre standard.

2. Gestion des relations un-à-un et un-à-plusieurs

Attention : Lorsqu'on utilise des résultats imbriqués pour gérer des relations, il est impératif de définir explicitement les mappings via les balises id ou result dans le resultMap. Sans cela, MyBatis pourrait ne retourner que la dernière ligne lors du traitement de plusieurs enregistrements.

2.1 L'association (un-à-un)

L'élément association permet de gérer les relations un-à-un. Il nécessite de spécifier la propriété de l'objet à mapper et son type Java (javaType, généralement détecté automatiquement). MyBatis propose deux modes de chargement :

  • select : Exécute une autre requête SQL définie dans un autre mapping pour retourner l'objet associé. C'est plus flexible.
  • resultMap : Utilise un résultat imbriqué pour traiter le résultat d'une jointure SQL et le mapper directement sur l'objet associé.

Exemple : Une classe a un professeur principal.

Dans l'entité Classe : private Professeur professeurPrincipal;

Utilisation de l'attribut select :

<!-- Dans ClassMapper.xml -->
<resultMap id="classeResultMap" type="Classe">
    <id property="id" column="CLASSE_ID"/>
    <result property="nom" column="CLASSE_NOM"/>
    <association property="professeurPrincipal" column="PROFESSEUR_ID" select="getProfesseur"/>
</resultMap>

<select id="getClasseParID" parameterType="String" resultMap="classeResultMap">
    SELECT * FROM CLASSE_TBL WHERE CLASSE_ID = #{id}
</select>

<!-- Dans ProfesseurMapper.xml -->
<resultMap id="professeurResultMap" type="Professeur">
    <id property="id" column="PROFESSEUR_ID"/>
    <result property="nom" column="PROFESSEUR_NOM"/>
</resultMap>

<select id="getProfesseur" parameterType="String" resultMap="professeurResultMap">
    SELECT * FROM PROFESSEUR_TBL WHERE PROFESSEUR_ID = #{id}
</select>

Utilisation de l'attribut resultMap :

<resultMap id="classeResultMap" type="Classe">
    <id property="id" column="CLASSE_ID"/>
    <result property="nom" column="CLASSE_NOM"/>
    <association property="professeurPrincipal" column="PROFESSEUR_ID" resultMap="professeurResultMap"/>
</resultMap>

<select id="getClasseEtProfesseur" parameterType="String" resultMap="classeResultMap">
    SELECT * FROM CLASSE_TBL CT
    LEFT JOIN PROFESSEUR_TBL PT ON CT.PROFESSEUR_ID = PT.PROFESSEUR_ID
    WHERE CT.CLASSE_ID = #{id}
</select>

2.2 La collection (un-à-plusieurs)

L'élément collection gère les relations un-à-plusieurs. Il nécessite de spécifier la propriété (généralement une liste), son type Java (javaType, souvent ArrayList) et le type d'objets qu'elle contient (ofType). Les deux modes de chargement (select et resultMap) sont également disponibles.

Exemple : Une classe a plusieurs étudiants.

Dans l'entité Classe : private List<Etudiant> listeEtudiants;

Utilisation de l'attribut select :

<resultMap id="classeResultMap" type="Classe">
    <id property="id" column="CLASSE_ID"/>
    <result property="nom" column="CLASSE_NOM"/>
    <collection property="listeEtudiants" column="CLASSE_ID" javaType="ArrayList" ofType="Etudiant" select="getEtudiantsParClasseID"/>
</resultMap>

<select id="getClasseParID" parameterType="String" resultMap="classeResultMap">
    SELECT * FROM CLASSE_TBL WHERE CLASSE_ID = #{id}
</select>

<!-- Dans EtudiantMapper.xml -->
<resultMap id="etudiantResultMap" type="Etudiant">
    <id property="id" column="ETUDIANT_ID"/>
    <result property="nom" column="ETUDIANT_NOM"/>
</resultMap>

<select id="getEtudiantsParClasseID" parameterType="String" resultMap="etudiantResultMap">
    SELECT * FROM ETUDIANT_TBL WHERE CLASSE_ID = #{id}
</select>

Utilisation de l'attribut resultMap :

<resultMap id="classeResultMap" type="Classe">
    <id property="id" column="CLASSE_ID"/>
    <result property="nom" column="CLASSE_NOM"/>
    <collection property="listeEtudiants" column="CLASSE_ID" javaType="ArrayList" ofType="Etudiant" resultMap="etudiantResultMap"/>
</resultMap>

<select id="getClasseEtEtudiants" parameterType="String" resultMap="classeResultMap">
    SELECT * FROM CLASSE_TBL CT
    LEFT JOIN ETUDIANT_TBL ET ON CT.CLASSE_ID = ET.CLASSE_ID
    WHERE CT.CLASSE_ID = #{id}
</select>

3. Mapping dynamique avec le discriminateur

L'élément discriminator permet de définir un mapping dynamique basé sur la valeur d'une colonne.

Exemple de scénario : Récupération des informations sur le lycée d'un étudiant. Selon la valeur de la colonne duree (durée d'études dans l'établissement), on utilise soit la colonne college (collège), soit la colonne lycee (lycée) pour alimenter la propriété lycee de l'objet.

public class Etudiant {
    private long id;
    private String nom;
    private String college;
    private String lycee;
    private int duree; // Durée dans l'établissement

    // Getters et setters
    // Constructeur sans argument
}

<select id="getEtudiant" resultMap="rm">
  SELECT ID, Nom, College, Lycee, duree FROM TEtudiant
</select>

<resultMap id="rm" type="Etudiant">
  <result column="college" property="college"/>

  <discriminator column="duree" javaType="_int">
    <case value="4" resultType="Etudiant">
      <result column="college" property="lycee"/>
    </case>
    <case value="5" resultMap="dynamicRM"/>
    <case value="6" resultMap="dynamicRM"/>
  </discriminator>
</resultMap>

<resultMap id="dynamicRM" type="Etudiant">
  <result column="college" property="lycee"/>
</resultMap>

Note importante : Les attributs resultType (dans les balises case) et type (dans la balise resultMap "dynamicRM") ne pointent pas directement vers le type de retour final de l'objet métier. Ils servent à définir un nouveau contexte de mapping dans lequel les sous-balises id et result peuvent redéfinir le mapping par défaut.

4. Attributs communs aux balises internes

Les balises id, result, idArg, arg et discriminator partagent certains attributs :

  • javaType : Nom complet de la classe ou alias du type Java.
  • jdbcType : Type JDBC, utile pour les colonnes pouvant être nulles lors des opérations CUD.
  • typeHandler : Nom complet de la classe ou alias du gestionnaire de type personnalisé.
  • column : Nom ou alias de la colonne dans le résultat SQL, utilisé par resultSet.getString(columnName).

Étiquettes: MyBatis resultMap mapping de résultats association collection

Publié le 29 juin à 21h47