web-dev-qa-db-fra.com

Dans une association JPA OneToMany / ManyToOne bidirectionnelle, qu'entend-on par "l'inverse de l'association"?

Dans ces exemples sur référence d'annotation JPA TopLink :

Exemple 1-59 @OneToMany - Classe client avec génériques

@Entity
public class Customer implements Serializable {
    ...
    @OneToMany(cascade=ALL, mappedBy="customer")
    public Set<Order> getOrders() { 
        return orders; 
    }
    ...
}

Exemple 1-60 @ManyToOne - Classe de commande avec génériques

@Entity
public class Order implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="CUST_ID", nullable=false)
    public Customer getCustomer() { 
        return customer; 
    }
    ...
}

Il me semble que l'entité Customer est le propriétaire de l'association. Cependant, dans l'explication de l'attribut mappedBy du même document, il est écrit que:

si la relation est bidirectionnelle, définissez l'élément mappedBy situé du côté inverse (non propriétaire) de l'association sur le nom du champ ou de la propriété propriétaire de la relation, comme indiqué dans l'exemple 1-60.

Cependant, si je ne me trompe pas, il semble que dans l'exemple, le mappedBy soit en fait spécifié du côté propriétaire de l'association, plutôt que du côté non propriétaire.

Donc ma question est fondamentalement:

  1. Dans une association bidirectionnelle (un à plusieurs/plusieurs à un), laquelle des entités est le propriétaire? Comment pouvons-nous désigner One Side comme propriétaire? Comment pouvons-nous désigner le côté Many comme propriétaire?

  2. Qu'entend-on par "l'inverse de l'association"? Comment pouvons-nous désigner le côté un comme étant l'inverse? Comment pouvons-nous désigner le côté Many comme l'inverse?

160
Behrang

Pour comprendre cela, vous devez prendre du recul. Dans OO, le client est propriétaire des commandes (les commandes sont une liste dans l'objet client). Il ne peut y avoir de commande sans client. Le client semble donc être le propriétaire des commandes.

Mais dans le monde SQL, un élément contient en réalité un pointeur sur l’autre. Puisqu'il y a 1 client pour N commandes, chaque commande contient une clé étrangère pour le client auquel elle appartient. C'est la "connexion" et cela signifie que l'ordre "possède" (ou contient littéralement) la connexion (information). C’est exactement le contraire du monde OO/modèle.

Cela peut aider à comprendre:

public class Customer {
     // This field doesn't exist in the database
     // It is simulated with a SQL query
     // "OO speak": Customer owns the orders
     private List<Order> orders;
}

public class Order {
     // This field actually exists in the DB
     // In a purely OO model, we could omit it
     // "DB speak": Order contains a foreign key to customer
     private Customer customer;
}

Le côté inverse est le OO "propriétaire" de l'objet, dans ce cas le client. Le client n'a pas de colonne dans la table pour stocker les commandes, vous devez donc lui indiquer où dans la commande table, il peut sauvegarder ces données (ce qui se passe via mappedBy).

Un autre exemple courant concerne les arbres avec des nœuds qui peuvent être à la fois parents et enfants. Dans ce cas, les deux champs sont utilisés dans une classe:

public class Node {
    // Again, this is managed by Hibernate.
    // There is no matching column in the database.
    @OneToMany(cascade = CascadeType.ALL) // mappedBy is only necessary when there are two fields with the type "Node"
    private List<Node> children;

    // This field exists in the database.
    // For the OO model, it's not really necessary and in fact
    // some XML implementations omit it to save memory.
    // Of course, that limits your options to navigate the tree.
    @ManyToOne
    private Node parent;
}

Ceci explique que la "clé étrangère" fonctionne beaucoup. Il existe une deuxième approche qui utilise une autre table pour maintenir les relations. Cela signifie que, pour notre premier exemple, vous avez trois tables: celle avec les clients, celle avec les commandes et une table à deux colonnes avec des paires de clés primaires (customerPK, orderPK).

Cette approche est plus souple que celle ci-dessus (elle peut facilement gérer un à un, plusieurs à un, un à plusieurs et même plusieurs à plusieurs). Le prix est que

  • c'est un peu plus lent (avoir à maintenir une autre table et à joindre utilise trois tables au lieu de deux),
  • la syntaxe de jointure est plus complexe (ce qui peut être fastidieux si vous devez écrire manuellement de nombreuses requêtes, par exemple lorsque vous essayez de déboguer quelque chose)
  • il est plus sujet aux erreurs car vous pouvez obtenir soudainement trop ou trop peu de résultats en cas de problème dans le code gérant la table de connexion.

C'est pourquoi je recommande rarement cette approche.

295
Aaron Digulla

Incroyablement, en 3 ans, personne n’a répondu à votre excellente question avec des exemples des deux manières de définir la relation.

Comme mentionné par d'autres, le côté "propriétaire" contient le pointeur (clé étrangère) dans la base de données. Vous pouvez désigner l'un des côtés comme propriétaire. Toutefois, si vous définissez le côté Un comme le propriétaire, la relation ne sera pas bidirectionnelle (l'inverse du côté "plusieurs" n'aura aucune connaissance de son "propriétaire"). Cela peut être souhaitable pour l’encapsulation/le couplage lâche:

// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
    @OneToMany(cascade = CascadeType.ALL)
    private List<Order> orders;
}

// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
    // @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}

La seule solution de mappage bidirectionnelle consiste à faire en sorte que le côté "plusieurs" possède son pointeur sur le "un" et à utiliser l'attribut @OneToMany "mappéPar". Sans l'attribut "mappedBy", Hibernate s'attend à un double mappage (la base de données comporterait à la fois la colonne join et la table join, ce qui est redondant (généralement indésirable)).

// "One" Customer as the inverse side of the relationship
public class Customer {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
    private List<Order> orders;
}

// "many" orders each own their pointer to a Customer
public class Order {
    @ManyToOne
    private Customer customer;
}
40
Steve Jones

L'entité qui a la table avec la clé étrangère dans la base de données est l'entité propriétaire et l'autre table pointée est l'entité inverse.

34
Venu

Règles simples des relations bidirectionnelles:

1.Pour les relations bidirectionnelles plusieurs à un, le côté nombreux est toujours le côté propriétaire de la relation. Exemple: 1 pièce contient plusieurs personnes (une personne appartient à une seule pièce) -> le côté propriétaire est une personne

2.Pour les relations bidirectionnelles un à un, le côté propriétaire correspond au côté qui contient la clé étrangère correspondante.

3.Pour les relations bidirectionnelles plusieurs à plusieurs, l'un des côtés peut être le propriétaire.

L'espoir peut vous aider.

13
Ken Block

Hibernate créera deux tables pour deux classes d’entités, Client et Commande.

Cas possibles:

  1. mappedBy n'est pas utilisé dans Customer.Java et Order.Java Class then->

    Du côté client, une nouvelle table sera créée [nom = CUSTOMER_ORDER] qui conservera le mappage de CUSTOMER_ID et ORDER_ID. Ce sont les clés primaires des tables de clients et de commandes. Du côté de la commande, une colonne supplémentaire est requise pour enregistrer le mappage d'enregistrement Customer_ID correspondant.

  2. mappedBy est utilisé dans Customer.Java [comme indiqué dans l'énoncé du problème] La table supplémentaire [CUSTOMER_ORDER] n'est maintenant pas créée. Une seule colonne dans la table des commandes

  3. mappedby est utilisé dans Order.Java Maintenant, une table supplémentaire sera créée par hibernate. [name = CUSTOMER_ORDER] La table de commande n'aura pas de colonne supplémentaire [ID_Client] pour le mappage.

N'importe quel côté peut être nommé propriétaire de la relation. Mais il vaut mieux choisir le côté xxxToOne.

Effet de codage -> Seul le côté Propriétaire de l'entité peut changer le statut de la relation. Dans l'exemple ci-dessous, la classe BoyFriend est propriétaire de la relation. même si Girlfriend veut rompre, elle ne le peut pas.

import javax.persistence.*;
import Java.util.ArrayList;
import Java.util.List;

@Entity
@Table(name = "BoyFriend21")
public class BoyFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Boy_ID")
    @SequenceGenerator(name = "Boy_ID", sequenceName = "Boy_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "BOY_NAME")
    private String name;

    @OneToOne(cascade = { CascadeType.ALL })
    private GirlFriend21 girlFriend;

    public BoyFriend21(String name) {
        this.name = name;
    }

    public BoyFriend21() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BoyFriend21(String name, GirlFriend21 girlFriend) {
        this.name = name;
        this.girlFriend = girlFriend;
    }

    public GirlFriend21 getGirlFriend() {
        return girlFriend;
    }

    public void setGirlFriend(GirlFriend21 girlFriend) {
        this.girlFriend = girlFriend;
    }
}

import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import Java.util.ArrayList;
import Java.util.List;

@Entity 
@Table(name = "GirlFriend21")
public class GirlFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Girl_ID")
    @SequenceGenerator(name = "Girl_ID", sequenceName = "Girl_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "GIRL_NAME")
    private String name;

    @OneToOne(cascade = {CascadeType.ALL},mappedBy = "girlFriend")
    private BoyFriend21 boyFriends = new BoyFriend21();

    public GirlFriend21() {
    }

    public GirlFriend21(String name) {
        this.name = name;
    }


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public GirlFriend21(String name, BoyFriend21 boyFriends) {
        this.name = name;
        this.boyFriends = boyFriends;
    }

    public BoyFriend21 getBoyFriends() {
        return boyFriends;
    }

    public void setBoyFriends(BoyFriend21 boyFriends) {
        this.boyFriends = boyFriends;
    }
}


import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import Java.util.Arrays;

public class Main578_DS {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
         try {
             configuration.configure("hibernate.cfg.xml");
         } catch (HibernateException e) {
             throw new RuntimeException(e);
         }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session = sessionFactory.openSession();
        session.beginTransaction();

        final BoyFriend21 clinton = new BoyFriend21("Bill Clinton");
        final GirlFriend21 monica = new GirlFriend21("monica lewinsky");

        clinton.setGirlFriend(monica);
        session.save(clinton);

        session.getTransaction().commit();
        session.close();
    }
}

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import Java.util.List;

public class Main578_Modify {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
        try {
            configuration.configure("hibernate.cfg.xml");
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session1 = sessionFactory.openSession();
        session1.beginTransaction();

        GirlFriend21 monica = (GirlFriend21)session1.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        BoyFriend21 boyfriend = monica.getBoyFriends();
        System.out.println(boyfriend.getName()); // It will print  Clinton Name
        monica.setBoyFriends(null); // It will not impact relationship

        session1.getTransaction().commit();
        session1.close();

        final Session session2 = sessionFactory.openSession();
        session2.beginTransaction();

        BoyFriend21 clinton = (BoyFriend21)session2.load(BoyFriend21.class,10);  // Bill clinton record

        GirlFriend21 girlfriend = clinton.getGirlFriend();
        System.out.println(girlfriend.getName()); // It will print Monica name.
        //But if Clinton[Who owns the relationship as per "mappedby" rule can break this]
        clinton.setGirlFriend(null);
        // Now if Monica tries to check BoyFriend Details, she will find Clinton is no more her boyFriend
        session2.getTransaction().commit();
        session2.close();

        final Session session3 = sessionFactory.openSession();
        session1.beginTransaction();

        monica = (GirlFriend21)session3.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        boyfriend = monica.getBoyFriends();

        System.out.println(boyfriend.getName()); // Does not print Clinton Name

        session3.getTransaction().commit();
        session3.close();
    }
}
3
HakunaMatata