Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Mapping d'une relation @OneToOne

JPA et la journalisation avec Hibernate Mapping d'une relation @ManyToOne



Accès rapide :
La vidéo
Introduction à la relation d'association de type « One-To-One »
Mapping d'une relation @OneToOne sans table d'association.
Implémentation, en base de données.
Implémentation du mapping JPA.
Test de la relation d'association.
Mapping d'une relation @OneToOne avec attribut inverse.
Définition du mapping avec un attribut inverse.
Utilisation du mapping
Mapping d'une relation @OneToOne avec table d'association.
Implémentation, en base de données.
Implémentation du mapping JPA.
Test de la relation d'association.

La vidéo

Cette vidéo vous présente les différentes techniques de mapping des relations de type @OneToOne (avec ou sans table de jointures) proposées par JPA.


Mapping JPA d'une relation d'association de type @OneToOne

Introduction à la relation d'association de type « One-To-One »

Nous allons voir, au travers de ce document, comment réaliser des associations de type « One-To-One » avec JPA et Hibernate. Une relation de type « One-To-One » permet de lier deux objets Java ensemble (ainsi que les données associées en base de données) et de permettre de naviguer d'une instance à l'autre et réciproquement.

Diagramme UML d'une relation « One-To-One »

Une difficulté réside dans le fait, qu'en base de données, il existe trois manière de réaliser une telle association. JPA supporte ces trois manières et vous propose, pour chacune d'entre elles, un mapping adapté. Ces trois possibilités de réalisation d'une relation « One-To-One » seront ainsi nommées :

le terme de table d'association est aussi parfois appelé table de jointure. Il s'agit d'une table en base de données permettant d'associer deux enregistrements situés dans deux autres tables de la base de données en utilisant des « foreign keys ».
dans ce chapitre, nous continuons à travailler en mode console (hors WAR Java/Jakarte EE) avec le SGBDr (Système de Gestion de Bases de données Relationnelles) MariaDB, comme proposé dans les exemples précédents. Je vous renvoie vers les tutoriels précédents pour de plus amples informations à ce sujet.

Mettons en oeuvre chacune de ces possibilités en commençant par la première.

Mapping d'une relation @OneToOne sans table d'association.

Implémentation, en base de données.

Voici le code SQL permettant de créer une base de données (pour le SGBDr MariaDB) avec deux tables mises en association par une relation de type « One-To-One ».

-- ------------------------------------------------------------------------------
-- - Reconstruction de la base de données                                     ---
-- ------------------------------------------------------------------------------

DROP DATABASE IF EXISTS OneToOne;
CREATE DATABASE OneToOne;
USE OneToOne;


-- -----------------------------------------------------------------------------
-- - Construction de la table des informations utilisateurs                  ---
-- -----------------------------------------------------------------------------

CREATE TABLE T_UserInformations (
    idInformations      int    PRIMARY KEY AUTO_INCREMENT,
    address             text,
    city                text,
    email               text,
    phoneNumber         text
);

INSERT INTO T_UserInformations (address, city, email, phoneNumber) 
VALUES ( 'Inconnue', 'La matrice', 'neo@matrix.com', '1234567890' ),
       ( 'rue du Faucon', 'L''étoile noire', 'luke@glaforce.wars', '0147258369' ),
       ( '1997, Manhattan', 'New York', 'snake@carpenter.com', '9638527410' ),
       ( 'Nostromo', 'La bas', 'ripley@nostromo.alien', '9876543210' ),
       ( 'SIS Building', 'London', '007@mi6.uk', '7007007007' );

SELECT * FROM T_UserInformations;

-- -----------------------------------------------------------------------------
-- - Construction de la table des utilisateurs                               ---
-- -----------------------------------------------------------------------------

CREATE TABLE T_Users (
    idUser              int         PRIMARY KEY AUTO_INCREMENT,
    login               varchar(20) NOT NULL,
    password            varchar(20) NOT NULL,
    connectionNumber    int         NOT NULL DEFAULT 0,
    idUserInformations  int         UNIQUE NOT NULL REFERENCES T_UserInformations(idInformations)
);

INSERT INTO T_Users (login, password, idUserInformations) 
VALUES ( 'Anderson',    'Neo', 1 ),
       ( 'Skywalker',   'Luke', 2 ),
       ( 'Plissken',    'Snake', 3 ),
       ( 'Ripley',      'Ellen', 4 ),
       ( 'Bond',        'James', 5 );

SELECT * FROM T_Users;
Le fichier Database.sql
peut-être que certains d'entre vous se demandent pourquoi stocker les données d'une personne dans une autre table. La question est pertinente : on aurait pu imaginer une seule table avec l'intégralité des données pour chaque utilisateur. Pour autant, parfois il peut être judicieux de séparer certaines informations sur différentes tables. Pour l'exemple nous accepterons le choix ici fait.
la contrainte d'unicité sur la clé de référence idUserInformations permet de garantir la cohérence de la relation « One-To-One » en base de données.

Pour créer votre base de données, connectez-vous en mode ligne de commande à votre serveur puis exécutez l'ordre suivant :

MariaDB [(none)]> source Database.sql
Query OK, 2 rows affected, 2 warnings (0.034 sec)

Query OK, 1 row affected (0.000 sec)

Database changed
Query OK, 0 rows affected (0.015 sec)

Query OK, 5 rows affected (0.003 sec)
Records: 5  Duplicates: 0  Warnings: 0

+----------------+-----------------+----------------+-----------------------+-------------+
| idInformations | address         | city           | email                 | phoneNumber |
+----------------+-----------------+----------------+-----------------------+-------------+
|              1 | Inconnue        | La matrice     | neo@matrix.com        | 1234567890  |
|              2 | rue du Faucon   | L'étoile noire | luke@glaforce.wars    | 0147258369  |
|              3 | 1997, Manhattan | New York       | snake@carpenter.com   | 9638527410  |
|              4 | Nostromo        | La bas         | ripley@nostromo.alien | 9876543210  |
|              5 | SIS Building    | London         | 007@mi6.uk            | 7007007007  |
+----------------+-----------------+----------------+-----------------------+-------------+
5 rows in set (0.000 sec)

Query OK, 0 rows affected (0.009 sec)

Query OK, 5 rows affected (0.002 sec)
Records: 5  Duplicates: 0  Warnings: 0

+--------+-----------+----------+------------------+--------------------+
| idUser | login     | password | connectionNumber | idUserInformations |
+--------+-----------+----------+------------------+--------------------+
|      1 | Anderson  | Neo      |                0 |                  1 |
|      2 | Skywalker | Luke     |                0 |                  2 |
|      3 | Plissken  | Snake    |                0 |                  3 |
|      4 | Ripley    | Ellen    |                0 |                  4 |
|      5 | Bond      | James    |                0 |                  5 |
+--------+-----------+----------+------------------+--------------------+
5 rows in set (0.000 sec)

MariaDB [OneToOne]> 

Implémentation du mapping JPA.

Il nous faut maintenant définir les deux classes d'entités associées à nos tables et y ajouter les annotations JPA de mapping. En premier lieu, voici le code de la classe UserInformations.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
package fr.koor.webstore.business;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity @Table(name = "T_UserInformations")
public class UserInformations {

    @Id  @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int     idInformations;
    
    private String  address;
    
    private String  city;
    
    private String  email;
    
    private String  phoneNumber;
    
    public UserInformations() {
        this( "unknown", "unknown", "unknown", "unknown" );
    }
    
    public UserInformations( String address, String city, String email, String phoneNumber ) {
        this.setAddress( address );
        this.setCity( city );
        this.setEmail( email );
        this.setPhoneNumber( phoneNumber );
    }
    
    public int getIdInformations() {
        return idInformations;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
    public String getCity() {
        return city;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getEmail() {
        return email;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public String getPhoneNumber() {
        return phoneNumber;
    }
    
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return "\tidInformations=" + idInformations + "\n\taddress=" + address 
             + "\n\tcity=" + city + "\n\temail=" + email + "\n\tphoneNumber=" + phoneNumber + "\n";
    }
    
}
La classe UserInformations et son mapping JPA
comme les attributs address, city, email et phoneNumber ont les mêmes noms que leurs colonnes associées en base de données, il n'est pas nécessaire de les marquer via des annotations JPA.

Maintenant il nous faut définir la classe User qui réalise une relation de type « One-To-One » vers la classe UserInformations. Notez que dans notre cas, le lien en base de données et dans le même sens que le lien entre nos classes : c'est la table T_Users qui contient la clé de référence vers la table T_UserInformations et c'est la classe User qui permet d'atteindre une instance de type UserInformations. Dans ce cas, il nous faut utiliser le couple d'annotation JPA @OneToOne et @JoinColumn.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
@Entity @Table(name = "T_Users")
public class User {

    // Autres attributs

    @OneToOne  @JoinColumn( name="idUserInformations" )
    private UserInformations userInformations;
            
    // Suite de la classe

}
Exemple d'une relation de type « One-To-One »

Si vous le souhaitez, il est possible de cascader certaines actions initiées sur une instance de la classe User à l'instance de type UserInformations qui lui est associée. Les actions pouvant être cascadées sont : DETACH, MERGE, PERSIST REFRESH et REMOVE. Par exemple, si l'on cascade le REMOVE, une suppression d'une instance d'utilisateur supprimera automatiquement ses informations associées. Nous reviendrons sur ces possibilités ultérieurement. En utilisant la constante CascadeType.ALL on demande à cascader toutes ces actions.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
@Entity @Table(name = "T_Users")
public class User {

    // Autres attributs

    @OneToOne( cascade = CascadeType.ALL ) 
    @JoinColumn( name="idUserInformations" )
    private UserInformations userInformations;
            
    // Suite de la classe

}
Exemple d'une relation de type « One-To-One » avec cascade des actions

Il est aussi possible d'imposer la présence d'une instance de type UserInformations pour chaque utilisateur en fixant l'attribut nullable à false sur l'annotation @JoinColumn.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
@Entity @Table(name = "T_Users")
public class User {

    // Autres attributs

    @OneToOne 
    @JoinColumn( name="idUserInformations", nullable=false )
    private UserInformations userInformations;
            
    // Suite de la classe

}
Exemple d'une relation de type « One-To-One » n'acceptant pas des associations inéxistantes.

Voici le code complet de la classe User.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
package fr.koor.webstore.business;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity @Table(name = "T_Users")
public class User {

    @Id  @GeneratedValue( strategy=GenerationType.IDENTITY )
    private int idUser;
    
    private String login;
    
    private String password;
    
    private int connectionNumber;
    
    @OneToOne  
    @JoinColumn( name="idUserInformations", nullable=false )
    private UserInformations userInformations;
    

    public User() { }
    
    public User( String login, String password, int connectionNumber ) {
        super();
        this.setLogin( login );
        this.setPassword( password );
        this.setConnectionNumber( connectionNumber );
    }

    
    public int getIdUser() {
        return idUser;
    }
    
    public String getLogin() {
        return login;
    }
    
    public void setLogin(String login) {
        this.login = login;
    }

    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    public int getConnectionNumber() {
        return connectionNumber;
    }
    
    public void setConnectionNumber(int connectionNumber) {
        this.connectionNumber = connectionNumber;
    }
    
    public UserInformations getUserInformations() {
        return userInformations;
    }

    public void setUserInformations(UserInformations userInfos) {
        this.userInformations = userInfos;
    }

    
    public String toString() {
        return this.idUser + ": " + this.login + "/" + this.password 
             + " - " + this.connectionNumber + " connexion(s)";
    }
    
}
La classe User et son mapping JPA

Test de la relation d'association.

Pour configurer le projet JPA, il est nécessaire de fournir le fichier META-INF/persistence.xml. N'oubliez pas qu'il doit être accessible à partir de CLASSPATH. En voici sa définition.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
                   http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
             
    <persistence-unit name="WebStore">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        
        <class>fr.koor.webstore.business.User</class> 
        <class>fr.koor.webstore.business.UserInformations</class> 
        
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:mariadb://localhost/OneToOne" />
            <property name="javax.persistence.jdbc.user" value="root" />
            <property name="javax.persistence.jdbc.password" value="" />
            
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.format_sql" value="false" />
        </properties>
    </persistence-unit>
    
</persistence>
Le fichier de configuration JPA/Hibernate

Il vous faut, bien entendu un fichier de configuration pour Log4J (du moins, si vous souhaitez l'utiliser) : je vous renvoie à ce sujet sur le chapitre précédent pour de plus amples informations.

Nous allons commencer par charger une instance d'utilisateur par sa clé primaire en invoquant la méthode entityManager.find. Nous descendons ensuite dans l'instance contenant ses informations en invoquant la méthode user.getUserInformations(). Normalement, JPA doit se débrouiller de charger les informations de manière automatique (grâce au mapping). Voici l'exemple de code associé à ce scénario.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
package fr.koor.webstore;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import fr.koor.webstore.business.User;
import fr.koor.webstore.business.UserInformations;

public class Console {

    public static void main(String[] args) throws Exception {
        EntityManagerFactory entityManagerFactory = null;
        EntityManager entityManager = null;
        try {
            entityManagerFactory = Persistence.createEntityManagerFactory("WebStore");
            entityManager = entityManagerFactory.createEntityManager();

            User user = entityManager.find( User.class, 1 );
            System.out.println( user );
            UserInformations informations = user.getUserInformations();
            System.out.println( informations );
       } finally {
            if ( entityManager != null ) entityManager.close();
            if ( entityManagerFactory != null ) entityManagerFactory.close();
        }
    }
}
Exemple de chargement d'un utilisateur et de ses informations.

Voici les résultats produits par cet exemple.

[main] WARN  | org.hibernate.orm.connections.pooling                        | HHH10001002: Using Hibernate built-in connection pool (not for production use!) (DriverManagerConnectionProviderImpl.java:70)
1: Anderson/Neo - 0 connexion(s)
    idInformations=1
    address=Inconnue
    city=La matrice
    email=neo@matrix.com
    phoneNumber=1234567890

Afin de tester la cascade, je vous propose de supprimer un utilisateur et de constater le contenu de la base de données. Voici un exemple de code effectuant la demande de suppression d'un utilisateur.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
package fr.koor.webstore;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import fr.koor.webstore.business.User;
import fr.koor.webstore.business.UserInformations;

public class Console {

    public static void main(String[] args) throws Exception {
        EntityManagerFactory entityManagerFactory = null;
        EntityManager entityManager = null;
        try {
            entityManagerFactory = Persistence.createEntityManagerFactory("WebStore");
            entityManager = entityManagerFactory.createEntityManager();

            EntityTransaction trans = entityManager.getTransaction();
            trans.begin();

            User user = entityManager.find( User.class, 1 );
            entityManager.remove( user );       // Suppression de l'utilisateur

            trans.commit();            
       } finally {
            if ( entityManager != null ) entityManager.close();
            if ( entityManagerFactory != null ) entityManagerFactory.close();
        }
    }
}
Exemple de suppression d'un utilisateur et de ses informations.

Une fois le code exécuté, connecter vous à votre base de données et constater les changements.

$> mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 26
Server version: 10.3.17-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use OneToOne
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [OneToOne]> select * from T_Users;
+--------+-----------+----------+------------------+--------------------+
| idUser | login     | password | connectionNumber | idUserInformations |
+--------+-----------+----------+------------------+--------------------+
|      2 | Skywalker | Luke     |                0 |                  2 |
|      3 | Plissken  | Snake    |                0 |                  3 |
|      4 | Ripley    | Ellen    |                0 |                  4 |
|      5 | Bond      | James    |                0 |                  5 |
+--------+-----------+----------+------------------+--------------------+
4 rows in set (0.001 sec)

MariaDB [OneToOne]> select * from T_UserInformations;
+----------------+-----------------+----------------+-----------------------+-------------+
| idInformations | address         | city           | email                 | phoneNumber |
+----------------+-----------------+----------------+-----------------------+-------------+
|              2 | rue du Faucon   | L'étoile noire | luke@glaforce.wars    | 0147258369  |
|              3 | 1997, Manhattan | New York       | snake@carpenter.com   | 9638527410  |
|              4 | Nostromo        | La bas         | ripley@nostromo.alien | 9876543210  |
|              5 | SIS Building    | London         | 007@mi6.uk            | 7007007007  |
+----------------+-----------------+----------------+-----------------------+-------------+
4 rows in set (0.000 sec)

MariaDB [OneToOne]>
vous pouvez reconstruire la base de données en exécutant de nouveau la commande source Database.sql.

Mapping d'une relation @OneToOne avec attribut inverse.

On parle d'attribut inverse quand la relation en base de données est dans le sens opposé à la relation de navigation dans votre modèle objet. Ce cas intervient obligatoirement quand vous cherchez à mettre en oeuvre une relation « One-To-One » bidirectionnelle, étant donné qu'une seule et unique table contient la colonne de mise en relation.

Définition du mapping avec un attribut inverse.

Nous allons repartir de l'exemple précédent et nous allons permettre, à partir de la classe UserInformations de retrouver l'instance de la classe User associée. Comme en base de données, la clé de référence est dans la table opposée, on doit dire quelle est la propriété de la classe User qui défini l'association (dans notre cas, la propriété userInformations). Le fait que la clé de référence soit localisé dans la table User est déduit du type de l'attribut user. Voici le code JPA permettant de définir la relation de navigation dans la classe UserInformations.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
package fr.koor.webstore.business;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity @Table(name = "T_UserInformations")
public class UserInformations {

    @Id  @GeneratedValue( strategy=GenerationType.IDENTITY )
    private int     idInformations;
    
    private String  address;
    
    private String  city;
    
    private String  email;
    
    private String  phoneNumber;
    
    @OneToOne( mappedBy = "userInformations" )
    private User user;
    
    public UserInformations() {
        this( "unknown", "unknown", "unknown", "unknown" );
    }
    
    public UserInformations( String address, String city, String email, String phoneNumber ) {
        this.setAddress( address );
        this.setCity( city );
        this.setEmail( email );
        this.setPhoneNumber( phoneNumber );
    }
    
    public int getIdInformations() {
        return idInformations;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
    public String getCity() {
        return city;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getEmail() {
        return email;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public String getPhoneNumber() {
        return phoneNumber;
    }
    
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public User getUser() {
        return user;
    }
    
    public void setUser(User user) {
        this.user = user;
    }
    
    @Override
    public String toString() {
        return "\tidInformations=" + idInformations + "\n\taddress=" + address 
             + "\n\tcity=" + city + "\n\temail=" + email + "\n\tphoneNumber=" + phoneNumber + "\n";
    }
    
}
La classe UserInformations et son mapping JPA

Utilisation du mapping

Cherchons maintenant à retrouver un utilisateur à partir de ses informations. Voici un exemple de code :

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
package fr.koor.webstore;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import fr.koor.webstore.business.User;
import fr.koor.webstore.business.UserInformations;

public class Console {

    public static void main(String[] args) throws Exception {
        EntityManagerFactory entityManagerFactory = null;
        EntityManager entityManager = null;
        try {
            entityManagerFactory = Persistence.createEntityManagerFactory("WebStore");
            entityManager = entityManagerFactory.createEntityManager();

            UserInformations infos = entityManager.find( UserInformations.class, 1 );
            User user = infos.getUser();
            System.out.println( user );

       } finally {
            if ( entityManager != null ) entityManager.close();
            if ( entityManagerFactory != null ) entityManagerFactory.close();
        }
    }
}
Utilisation d'une relation de type @OneToOne avec attribut inverse

Mapping d'une relation @OneToOne avec table d'association.

Pour rappel, le terme de table d'association est aussi parfois appelé table de jointure. Il s'agit d'une table en base de données permettant d'associer deux enregistrements situés dans deux autres tables de la base de données en utilisant des « foreign keys » (des clés étrangères en français).

Parfois, des tables en base de données sont déjà existantes, mais aucune relation ne les relie. Vous pourriez simplement ajouter une clé de référence dans l'une des deux tables pour établir la relation. Mais si d'autres applications utilisent déjà ces tables, l'ajout d'une nouvelle colonne peut poser soucis. Dans ce cas, l'ajout d'une table d'association vous permet de gérer votre problème d'association sans impacter les applications existantes. Le contre coup étant qu'il faut réaliser plus de jointures pour extraire vos données (c'est un peu moins performant).

Implémentation, en base de données.

Voici le code SQL permettant de créer une base de données (pour le SGBDr MariaDB) avec nos deux tables T_Users et T_UserInformations ainsi qu'une nouvelle table d'association, appelée T_Users_Informations_Associations permettant de réaliser notre relation de type « One-To-One ».

-- ------------------------------------------------------------------------------
-- - Reconstruction de la base de données                                     ---
-- ------------------------------------------------------------------------------

DROP DATABASE IF EXISTS OneToOne;
CREATE DATABASE OneToOne;
USE OneToOne;


-- -----------------------------------------------------------------------------
-- - Construction de la table des informations utilisateurs                  ---
-- -----------------------------------------------------------------------------

CREATE TABLE T_UserInformations (
    idInformations      int      PRIMARY KEY AUTO_INCREMENT,
    address             text,
    city                text,
    email               text,
    phoneNumber         text
);

INSERT INTO T_UserInformations (address, city, email, phoneNumber) 
VALUES ( 'Inconnue', 'La matrice', 'neo@matrix.com', '1234567890' ),
       ( 'rue du Faucon', 'L''étoile noire', 'luke@glaforce.wars', '0147258369' ),
       ( '1997, Manhattan', 'New York', 'snake@carpenter.com', '9638527410' ),
       ( 'Nostromo', 'La bas', 'ripley@nostromo.alien', '9876543210' ),
       ( 'SIS Building', 'London', '007@mi6.uk', '7007007007' );

SELECT * FROM T_UserInformations;

-- -----------------------------------------------------------------------------
-- - Construction de la table des utilisateurs                               ---
-- -----------------------------------------------------------------------------

CREATE TABLE T_Users (
    idUser              int         PRIMARY KEY AUTO_INCREMENT,
    login               varchar(20) NOT NULL,
    password            varchar(20) NOT NULL,
    connectionNumber    int         NOT NULL DEFAULT 0
);

INSERT INTO T_Users (login, password) 
VALUES ( 'Anderson',    'Neo' ),
       ( 'Skywalker',   'Luke' ),
       ( 'Plissken',    'Snake' ),
       ( 'Ripley',      'Ellen' ),
       ( 'Bond',        'James' );

SELECT * FROM T_Users;

-- -----------------------------------------------------------------------------
-- - Construction de la table d'association (de jointure)                    ---
-- -----------------------------------------------------------------------------

CREATE TABLE T_Users_Informations_Associations (
    idUser              int      UNIQUE NOT NULL REFERENCES T_Users(idUser),
    idUserInformations  int      UNIQUE NOT NULL REFERENCES T_UserInformations(idInformations)
);

INSERT INTO T_Users_Informations_Associations 
VALUES ( 1, 1 ), ( 2, 2 ), ( 3, 3 ), ( 4, 4 ), ( 5, 5 );
Fichier Database.sql

Pour construire la base de données, veuillez exécuter l'ordre source Database.sql dans la console de MariaDB.

Implémentation du mapping JPA.

Pour réaliser une relation d'association de type « One-To-One » avec table d'association, il faut encore utiliser l'annotation @OneToOne mais il va falloir la coupler à une annotation @JoinTable afin d'y spécifier les informations utiles à la jointure. Voici un extrait de code relatif à la définition d'une telle relation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
@Entity @Table(name = "T_Users")
public class User {

    // Autres attributs
    
    @OneToOne  
    @JoinTable( name = "T_Users_Informations_Associations",
                joinColumns = @JoinColumn( name = "idUser" ),
                inverseJoinColumns = @JoinColumn( name = "idUserInformations" ) )
    private UserInformations userInformations;
            
    // Suite de la classe
    
}
Mise en oeuvre d'une relation de type @OneToOne avec table d'association

Si vous souhaitez obtenir une relation d'association bidirectionnelle, vous devez aussi définir la relation dans la classe UserInformations : les noms de colonnes utilisées pour les paramètres joinColumns et inverseJoinColumns doivent être inversés.

Voici les codes complets de notre deux classes, en commençant par la classe User.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
package fr.koor.webstore.business;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity @Table(name = "T_Users")
public class User {

    @Id  @GeneratedValue( strategy=GenerationType.IDENTITY )
    private int idUser;
    
    private String login;
    
    private String password;
    
    private int connectionNumber;
    
    @OneToOne
    @JoinTable( name = "T_Users_Informations_Associations",
                joinColumns = @JoinColumn( name = "idUser" ),
                inverseJoinColumns = @JoinColumn( name = "idUserInformations" ) )
    private UserInformations userInformations;
    

    public User() { }
    
    public User( String login, String password, int connectionNumber ) {
        super();
        this.setLogin( login );
        this.setPassword( password );
        this.setConnectionNumber( connectionNumber );
    }

    
    public int getIdUser() {
        return idUser;
    }
    
    public String getLogin() {
        return login;
    }
    
    public void setLogin(String login) {
        this.login = login;
    }

    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    public int getConnectionNumber() {
        return connectionNumber;
    }
    
    public void setConnectionNumber(int connectionNumber) {
        this.connectionNumber = connectionNumber;
    }
    
    public UserInformations getUserInformations() {
        return userInformations;
    }

    public void setUserInformations(UserInformations userInfos) {
        this.userInformations = userInfos;
    }

    
    public String toString() {
        return this.idUser + ": " + this.login + "/" + this.password + " - " 
             + this.connectionNumber + " connexion(s)";
    }
    
}
La classe User et son mapping JPA

Et voici la classe UserInformations et son mapping JPA.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
package fr.koor.webstore.business;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity @Table(name = "T_UserInformations")
public class UserInformations {

    @Id  @GeneratedValue( strategy=GenerationType.IDENTITY )
    private int     idInformations;
    
    private String  address;
    
    private String  city;
    
    private String  email;
    
    private String  phoneNumber;
    
    @OneToOne
    @JoinTable( name = "T_Users_Informations_Associations",
                joinColumns = @JoinColumn( name = "idUserInformations" ),
                inverseJoinColumns = @JoinColumn( name = "idUser" ) )
    private User user;
    
    public UserInformations() {
        this( "unknown", "unknown", "unknown", "unknown" );
    }
    
    public UserInformations( String address, String city, String email, String phoneNumber ) {
        this.setAddress( address );
        this.setCity( city );
        this.setEmail( email );
        this.setPhoneNumber( phoneNumber );
    }
    
    public int getIdInformations() {
        return idInformations;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    
    public String getCity() {
        return city;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public String getEmail() {
        return email;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public String getPhoneNumber() {
        return phoneNumber;
    }
    
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public User getUser() {
        return user;
    }
    
    public void setUser(User user) {
        this.user = user;
    }
    
    @Override
    public String toString() {
        return "\tidInformations=" + idInformations + "\n\taddress=" + address 
             + "\n\tcity=" + city + "\n\temail=" + email + "\n\tphoneNumber=" + phoneNumber + "\n";
    }
    
}
La classe UserInformations et son mapping JPA

Test de la relation d'association.

Normalement, vous pouvez récupérer le fichier de configuration JPA proposé plus haut dans ce document. Aucune modification ne devrait être nécessaire, sauf si vous avez changé le nom de la base de données.

Vous devriez aussi pouvoir reprendre les exemples d'utilisation de vos classes précédemment proposés et ils devraient correctement fonctionner. Pour rappel, voici un petit exemple d'utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
package fr.koor.webstore;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import fr.koor.webstore.business.User;
import fr.koor.webstore.business.UserInformations;

public class Console {

    public static void main(String[] args) throws Exception {
        EntityManagerFactory entityManagerFactory = null;
        EntityManager entityManager = null;
        try {
            entityManagerFactory = Persistence.createEntityManagerFactory("WebStore");
            entityManager = entityManagerFactory.createEntityManager();

            User user = entityManager.find( User.class, 1 );
            System.out.println( user );
            System.out.println( user.getUserInformations() );            

       } finally {
            if ( entityManager != null ) entityManager.close();
            if ( entityManagerFactory != null ) entityManagerFactory.close();
        }
    }
}
Utilisation d'une relation de type @OneToOne avec table d'association


JPA et la journalisation avec Hibernate Mapping d'une relation @ManyToOne