Mapping en JPA 2.0 d’une table de jointure ayant des colonnes supplémentaires

Cet article est la suite d’un premier billet où j’ai présenté une première solution en JPA 1.0 à la problématique : Comment mapper, en JPA (Java Persistence API),  une table de jointure comportant des colonnes supplémentaires en plus des colonnes de clés étrangères constituant sa clé primaire ? S’il y a quelque chose à reprocher à cette première solution, c’est la contrainte de mapper deux fois les colonnes constituant la clé primaire de la table de jointure. Mais grâce l’annotation @MapsId, une nouvelle annotation propre à l’API JPA 2.0, on peut avoir une solution plus élégante. Mais avant de présenter la solution en utilisant cette annotation, je vous encourage à revenir au premier billet où j’ai bien présenté la problématique et l’exemple de modèle de données sur lequel je me suis basé.

La solution en JPA 2.0

Avec @MapsId, ce n’est plus la peine de mapper deux fois les colonnes constituant la clé primaire de la table de jointure comme on l’a fait dans le Listing 3 du premier billet (au niveau des champs de la classe id : EmployeConceptId avec @Column et au niveau des relations ManyToOne dans EmployeConcept avec @JoinColumn). En fait, le mapping est fait seulement au niveau de la relation. Le Listing 1 montre cette solution en JPA 2.0 avec @MapsId. (Dans la suite du billet je ferai référence à la classe EmployeConceptId par classe id).

<strong>Listing 1</strong>
<span style="color: #808080;">01</span> <span style="color: #646464;">@Embeddable</span>
<span style="color: #808080;">02</span> <span style="color: #7f0055;"><strong>public final class </strong></span><span style="color: #000000;">EmployeConceptId </span><span style="color: #7f0055;"><strong>implements </strong></span><span style="color: #000000;">Serializable </span><span style="color: #000000;">{</span>
<span style="color: #808080;">03</span>
<span style="color: #808080;">04</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Long employeId;</span>
<span style="color: #808080;">05</span>
<span style="color: #808080;">06</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Long conceptId;</span>
<span style="color: #808080;">07</span>
<span style="color: #808080;">08</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>public </strong></span><span style="color: #000000;">EmployeConceptId</span><span style="color: #000000;">() {</span>
<span style="color: #808080;">09</span>
<span style="color: #808080;">10</span> <span style="color: #ffffff;">    </span><span style="color: #000000;">}</span>
<span style="color: #808080;">11</span>
<span style="color: #808080;">12</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>public </strong></span><span style="color: #000000;">EmployeConceptId</span><span style="color: #000000;">(</span><span style="color: #000000;">Long employeId, Long conceptId</span><span style="color: #000000;">) {</span>
<span style="color: #808080;">13</span> <span style="color: #ffffff;">        </span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.employeId = employeId;</span>
<span style="color: #808080;">14</span> <span style="color: #ffffff;">        </span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.conceptId = conceptId;</span>
<span style="color: #808080;">15</span> <span style="color: #ffffff;">    </span><span style="color: #000000;">}</span>
<span style="color: #808080;">16</span>
<span style="color: #808080;">17</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>public </strong></span><span style="color: #7f0055;"><strong>boolean </strong></span><span style="color: #000000;">equals</span><span style="color: #000000;">(</span><span style="color: #000000;">Object obj</span><span style="color: #000000;">) {</span>
<span style="color: #808080;">18</span> <span style="color: #ffffff;">        </span><span style="color: #000000;">...</span>
<span style="color: #808080;">19</span> <span style="color: #ffffff;">    </span><span style="color: #000000;">}</span>
<span style="color: #808080;">20</span>
<span style="color: #808080;">21</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>public </strong></span><span style="color: #7f0055;"><strong>int </strong></span><span style="color: #000000;">hashCode</span><span style="color: #000000;">() {</span>
<span style="color: #808080;">22</span> <span style="color: #ffffff;">        </span><span style="color: #000000;">...</span>
<span style="color: #808080;">23</span> <span style="color: #ffffff;">    </span><span style="color: #000000;">}</span>
<span style="color: #808080;">24</span> <span style="color: #000000;">}</span>
<span style="color: #808080;">25</span>
<span style="color: #808080;">26</span> <span style="color: #646464;">@Entity</span>
<span style="color: #808080;">27</span> <span style="color: #7f0055;"><strong>public class </strong></span><span style="color: #000000;">EmployeConcept </span><span style="color: #000000;">{</span>
<span style="color: #808080;">28</span>
<span style="color: #808080;">29</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@EmbeddedId</span>
<span style="color: #808080;">30</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">EmployeConceptId id;</span>
<span style="color: #808080;">31</span>
<span style="color: #808080;">32</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@MapsId</span><span style="color: #000000;">(</span><span style="color: #2a00ff;">"employeId"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">33</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@ManyToOne</span>
<span style="color: #808080;">34</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@JoinColumn</span><span style="color: #000000;">(</span><span style="color: #000000;">name = </span><span style="color: #2a00ff;">"ec_emp_id"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">35</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Employe employe;</span>
<span style="color: #808080;">36</span>
<span style="color: #808080;">37</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@MapsId</span><span style="color: #000000;">(</span><span style="color: #2a00ff;">"conceptId"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">38</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@ManyToOne</span>
<span style="color: #808080;">39</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@JoinColumn</span><span style="color: #000000;">(</span><span style="color: #000000;">name = </span><span style="color: #2a00ff;">"ec_cpt_id"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">40</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Concept concept;</span>
<span style="color: #808080;">41</span>
<span style="color: #808080;">42</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>public </strong></span><span style="color: #000000;">EmployeConcept</span><span style="color: #000000;">(</span><span style="color: #000000;">Employe employe, Concept concept, </span><span style="color: #7f0055;"><strong>int </strong></span><span style="color: #000000;">note</span><span style="color: #000000;">) {</span>
<span style="color: #808080;">43</span> <span style="color: #ffffff;">        </span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.employe = employe;</span>
<span style="color: #808080;">44</span> <span style="color: #ffffff;">        </span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.concept = concept;</span>
<span style="color: #808080;">45</span>
<span style="color: #808080;">46</span> <span style="color: #ffffff;">        </span><span style="color: #000000;">employe.getConcepts</span><span style="color: #000000;">()</span><span style="color: #000000;">.add</span><span style="color: #000000;">(</span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">)</span><span style="color: #000000;">;</span>
<span style="color: #808080;">47</span> <span style="color: #ffffff;">        </span><span style="color: #000000;">concept.getEmployes</span><span style="color: #000000;">()</span><span style="color: #000000;">.add</span><span style="color: #000000;">(</span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">)</span><span style="color: #000000;">;</span>
<span style="color: #808080;">48</span>
<span style="color: #808080;">49</span> <span style="color: #ffffff;">        </span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.id = </span><span style="color: #7f0055;"><strong>new </strong></span><span style="color: #000000;">EmployeConceptId</span><span style="color: #000000;">(</span><span style="color: #000000;">concept.getId</span><span style="color: #000000;">()</span><span style="color: #000000;">, employe.getId</span><span style="color: #000000;">())</span><span style="color: #000000;">;</span>
<span style="color: #808080;">50</span> <span style="color: #ffffff;">        </span><span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.note = note;</span>
<span style="color: #808080;">51</span> <span style="color: #ffffff;">    </span><span style="color: #000000;">}</span>
<span style="color: #808080;">52</span>
<span style="color: #808080;">53</span> <span style="color: #ffffff;">    </span><span style="color: #3f7f5f;">// pas de setter pour employe ni pour concept </span>
<span style="color: #808080;">54</span> <span style="color: #000000;">}</span>
<span style="color: #808080;">55</span>
<span style="color: #808080;">56</span> <span style="color: #646464;">@Entity</span>
<span style="color: #808080;">57</span> <span style="color: #7f0055;"><strong>public class </strong></span><span style="color: #000000;">Employe </span><span style="color: #000000;">{</span>
<span style="color: #808080;">58</span>
<span style="color: #808080;">59</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@Id</span>
<span style="color: #808080;">60</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@Column</span><span style="color: #000000;">(</span><span style="color: #000000;">name = </span><span style="color: #2a00ff;">"emp_id"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">61</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Long id;</span>
<span style="color: #808080;">62</span>
<span style="color: #808080;">63</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@OneToMany</span><span style="color: #000000;">(</span><span style="color: #000000;">mappedBy = </span><span style="color: #2a00ff;">"employe"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">64</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Set&lt;EmployeConcept&gt; concepts = </span><span style="color: #7f0055;"><strong>new </strong></span><span style="color: #000000;">HashSet&lt;EmployeConcept&gt;</span><span style="color: #000000;">()</span><span style="color: #000000;">;</span>
<span style="color: #808080;">65</span>
<span style="color: #808080;">66</span> <span style="color: #ffffff;">    </span><span style="color: #3f7f5f;">// Getters and setters</span>
<span style="color: #808080;">67</span> <span style="color: #000000;">}</span>
<span style="color: #808080;">68</span>
<span style="color: #808080;">69</span> <span style="color: #646464;">@Entity</span>
<span style="color: #808080;">70</span> <span style="color: #7f0055;"><strong>public class </strong></span><span style="color: #000000;">Concept </span><span style="color: #000000;">{</span>
<span style="color: #808080;">71</span>
<span style="color: #808080;">72</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@Id</span>
<span style="color: #808080;">73</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@Column</span><span style="color: #000000;">(</span><span style="color: #000000;">name = </span><span style="color: #2a00ff;">"cpt_id"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">74</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Long id;</span>
<span style="color: #808080;">75</span>
<span style="color: #808080;">76</span> <span style="color: #ffffff;">    </span><span style="color: #646464;">@OneToMany</span><span style="color: #000000;">(</span><span style="color: #000000;">mappedBy = </span><span style="color: #2a00ff;">"concept"</span><span style="color: #000000;">)</span>
<span style="color: #808080;">77</span> <span style="color: #ffffff;">    </span><span style="color: #7f0055;"><strong>private </strong></span><span style="color: #000000;">Set&lt;EmployeConcept&gt; employes = </span><span style="color: #7f0055;"><strong>new </strong></span><span style="color: #000000;">HashSet&lt;EmployeConcept&gt;</span><span style="color: #000000;">()</span><span style="color: #000000;">;</span>
<span style="color: #808080;">78</span>
<span style="color: #808080;">79</span> <span style="color: #ffffff;">    </span><span style="color: #3f7f5f;">// Getters and setters</span>
<span style="color: #808080;">80</span> <span style="color: #000000;">}</span>

Réellement, le Listing 1 n’est que  le Listing 3 du premier billet avec  quelques modifications :

  • On enlève les @Column qui se trouvaient au niveau des attributs de la classe id.
  • On enlève insertable = false et updatable = false des @JoinColumn au niveau des relations ManyToOne car elles ne sont plus en lecture seule; c’est logique puisque ce sont elles, les seules maintenant,  qui mappent les colonnes de la clé primaire de la table de jointure. Cependant on ne définira pas de setters pour ces relations. Comme expliqué dans le premier billet : ces relations mappent les colonnes qui forment la clé primaire de la table de jointure qui dépendent des valeurs des clés primaires d’autres tables. Or une fois la clé primaire d’un enregistrement est définie, elle ne peut plus être changée.
  • On ajoute @MapsId à chaque relation ManyToOne pour indiquer que le mapping de celle-ci spécifie aussi le mapping de l’attribut de la classe id correspondant [NDLR : comme son nom l’indique]. Mais attention, EmployeConcept a deux relations ManyToOne et la classe id a deux champs qui composent l’id. Alors pour savoir quelle relation mappe quel attribut de la classe id, @MapsId doit spécifier (entre parenthèses) le nom de l’attribut de la classe id qu’elle mappe.

Les Listing 6 du premier billet permettant d’ajouter un lien entre un employé et un concept ; et le Listing 7 du même billet permettant de supprimer un lien restent valables.

Pour pouvoir persister un lien, l’implémentation du constructeur d’EmployeConcept ne change pas. Elle garantie l’intégrité référentielle entre les entités java. Mais les répercutions derrière les instructions changent. En effet, ce sont les attributs relations (employe et concept de EmployeConcept) qui sont devenus responsables de la modification au niveau de la base de données. Les attributs de la classe id ne sont qu’en lecture seule. Plus précisément, ce sont les instructions :

<strong>Listing 2</strong>
<span style="color: #808080;">1</span> <span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.employe = employe;</span>
<span style="color: #808080;">2</span> <span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.concept = concept;</span>

qui sont derrière le déclenchement l’insertion SQL. Et l’instanciation d’EmployeConceptId n’a pas l’obligation d’initier les attributs de cette classe id et on aurait pu remplacer :

<strong>Listing 3</strong>
<span style="color: #808080;">1</span> <span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.id = </span><span style="color: #7f0055;"><strong>new </strong></span><span style="color: #000000;">EmployeConceptId</span><span style="color: #000000;">(</span><span style="color: #000000;">concept.getId</span><span style="color: #000000;">()</span><span style="color: #000000;">, employe.getId</span><span style="color: #000000;">())</span><span style="color: #000000;">;</span>

par :

<strong>Listing 4</strong>
<span style="color: #808080;">1</span> <span style="color: #7f0055;"><strong>this</strong></span><span style="color: #000000;">.id = </span><span style="color: #7f0055;"><strong>new </strong></span><span style="color: #000000;">EmployeConceptId</span><span style="color: #000000;">()</span><span style="color: #000000;">;</span>

 

Mais il reste obligatoire d’instancier l’id d’EmployeConcept (il ne peut pas être null).

Je termine par un point qui doit être respecté dans les deux solutions. L’attribut de la classe id correspondant à l’attribut de la relation doit être de même type que l’attribut de l’id de l’entité cible de la relation. Par exemple, l’attribut employeId de la classe id correspondant à la relation @ManyToOne employe, de type Employe, d’EmployeConcept, doit être de même type, Long, que l’id de l’entité Employe.

Conclusion

En résumé, j’ai montré dans ce billet le mapping, en JPA 2.0, d’une table de jointure qui comporte des colonnes supplémentaires ne faisant pas partie des colonnes constituant la clé primaire de cette table. Chaque colonne de cette clé primaire référence une table différente.

Par ailleurs, il ne vous aura pas échappé que dans mon exemple les attributs de la classe id sont de type simple. On aurait pu avoir une classe id dont les attributs sont de type complexe et là je vous laisse le loisir de mapper ces cas.  Un petit indice : vous aurez besoin d’une @JoinColumns au lieu d’une simple @JoinColumn.

Une réflexion au sujet de « Mapping en JPA 2.0 d’une table de jointure ayant des colonnes supplémentaires »

  • 27 juin 2012 à 16 h 32 min
    Permalink

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *