Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 7. Mapper une collection

## 7.1. Collections persistantes

Naturally Hibernate also allows to persist collections. These persistent collections can contain almost any other Hibernate type, including: basic types, custom types, components and references to other entities. The distinction between value and reference semantics is in this context very important. An object in a collection might be handled with "value" semantics (its life cycle fully depends on the collection owner), or it might be a reference to another entity with its own life cycle. In the latter case, only the "link" between the two objects is considered to be a state held by the collection.

As a requirement persistent collection-valued fields must be declared as an interface type (see Exemple 7.2, « Collection mapping using @OneToMany and @JoinColumn »). The actual interface might be `java.util.Set`, `java.util.Collection`, `java.util.List`, `java.util.Map`, `java.util.SortedSet`, `java.util.SortedMap` or anything you like ("anything you like" means you will have to write an implementation of `org.hibernate.usertype.UserCollectionType`).

Notice how in Exemple 7.2, « Collection mapping using @OneToMany and @JoinColumn » the instance variable `parts` was initialized with an instance of `HashSet`. This is the best way to initialize collection valued properties of newly instantiated (non-persistent) instances. When you make the instance persistent, by calling `persist()`, Hibernate will actually replace the `HashSet` with an instance of Hibernate's own implementation of `Set`. Be aware of the following error:

Exemple 7.1. Hibernate uses its own collection implementations

    Cat cat = new DomesticCat();

    Cat kitten = new DomesticCat();

    ....

    Set kittens = new HashSet();

    kittens.add(kitten);

    cat.setKittens(kittens);

    session.persist(cat);

    kittens = cat.getKittens(); // Okay, kittens collection is a Set

    (HashSet) cat.getKittens(); // Error!

Les collections persistantes injectées par Hibernate se comportent de la même manière que `HashMap`, `HashSet`, `TreeMap`, `TreeSet` ou `ArrayList`, selon le type de l'interface.

Les instances des collections ont le comportement habituel des types de valeurs. Elles sont automatiquement persistées quand elles sont référencées par un objet persistant et automatiquement effacées quand elles sont déréférencées. Si une collection est passée d'un objet persistant à un autre, ses éléments peuvent être déplacés d'une table à une autre. Deux entités ne peuvent pas partager une référence vers une même instance de collection. Dû au modèle relationnel sous-jacent, les propriétés contenant des collections ne supportent pas la sémantique de la valeur null ; Hibernate ne fait pas de distinction entre une référence de collection nulle et une collection vide.

## Note

Use persistent collections the same way you use ordinary Java collections. However, ensure you understand the semantics of bidirectional associations (see Section 7.3.2, « Associations bidirectionnelles »).

## 7.2. How to map collections

Using annotations you can map `Collection`s, `List`s, `Map`s and `Set`s of associated entities using @OneToMany and @ManyToMany. For collections of a basic or embeddable type use @ElementCollection. In the simplest case a collection mapping looks like this:

Exemple 7.2. Collection mapping using @OneToMany and @JoinColumn

    @Entity

    public class Product {

        private String serialNumber;

        private Set<Part> parts = new HashSet<Part>();

        @Id

        public String getSerialNumber() { return serialNumber; }

        void setSerialNumber(String sn) { serialNumber = sn; }

        @OneToMany

        @JoinColumn(name="PART_ID")

        public Set<Part> getParts() { return parts; }

        void setParts(Set parts) { this.parts = parts; }

    }

    @Entity

    public class Part {

       ...

    }

Product describes a unidirectional relationship with Part using the join column PART_ID. In this unidirectional one to many scenario you can also use a join table as seen in Exemple 7.3, « Collection mapping using @OneToMany and @JoinTable ».

Exemple 7.3. Collection mapping using @OneToMany and @JoinTable

    @Entity

    public class Product {

        private String serialNumber;

        private Set<Part> parts = new HashSet<Part>();

        @Id

        public String getSerialNumber() { return serialNumber; }

        void setSerialNumber(String sn) { serialNumber = sn; }

        @OneToMany

        @JoinTable(

                name="PRODUCT_PARTS",

                joinColumns = @JoinColumn( name="PRODUCT_ID"),

                inverseJoinColumns = @JoinColumn( name="PART_ID")

        )

        public Set<Part> getParts() { return parts; }

        void setParts(Set parts) { this.parts = parts; }

    }

    @Entity

    public class Part {

       ...

    }

Without describing any physical mapping (no `@JoinColumn` or `@JoinTable`), a unidirectional one to many with join table is used. The table name is the concatenation of the owner table name, _, and the other side table name. The foreign key name(s) referencing the owner table is the concatenation of the owner table, _, and the owner primary key column(s) name. The foreign key name(s) referencing the other side is the concatenation of the owner property name, _, and the other side primary key column(s) name. A unique constraint is added to the foreign key referencing the other side table to reflect the one to many.

Lets have a look now how collections are mapped using Hibernate mapping files. In this case the first step is to chose the right mapping element. It depends on the type of interface. For example, a `<set>` element is used for mapping properties of type `Set`.

Exemple 7.4. Mapping a Set using <set>

    <class name="Product">

        <id name="serialNumber" column="productSerialNumber"/>

        <set name="parts">

            <key column="productSerialNumber" not-null="true"/>

            <one-to-many class="Part"/>

        </set>

    </class>

In Exemple 7.4, « Mapping a Set using <set> » a one-to-many association links the `Product` and `Part` entities. This association requires the existence of a foreign key column and possibly an index column to the `Part` table. This mapping loses certain semantics of normal Java collections:

- Une instance de la classe de l'entité contenue ne peut pas appartenir à plus d'une instance de la collection.

- Une instance de la classe de l'entité contenue peut ne pas apparaître à plus plus d'une valeur d'index de la collection.

Looking closer at the used `<one-to-many>` tag we see that it has the following options.

Exemple 7.5. options of <one-to-many> element

    <one-to-many
            class="ClassName"
            not-found="ignore|exception"
            entity-name="EntityName"
            node="element-name"
            embed-xml="true|false"
        />

| `class` (requis) : le nom de la classe associée.
| `not-found` (optionnel - par défaut `exception`) : spécifie comment les identifiants cachés qui référencent des lignes manquantes seront gérés : `ignore` traitera une ligne manquante comme une association nulle.
| `entity-name` (optionnel) : le nom de l'entité de la classe associée, comme une alternative à `class`.

Notez que l'élément `<one-to-many>` n'a pas besoin de déclarer de colonnes. Il n'est pas non plus nécessaire de spécifier le nom de la `table` à aucun endroit.

## Avertissement

If the foreign key column of a `<one-to-many>` association is declared `NOT NULL`, you must declare the `<key>` mapping `not-null="true"` or use a bidirectional association with the collection mapping marked `inverse="true"`. See Section 7.3.2, « Associations bidirectionnelles ».

Apart from the `<set>` tag as shown in Exemple 7.4, « Mapping a Set using <set> », there is also `<list>`, `<map>`, `<bag>`, `<array>` and `<primitive-array>` mapping elements. The `<map>` element is representative:

Exemple 7.6. Elements of the <map> mapping

    <map
        name="propertyName"
        table="table_name"
        schema="schema_name"
        lazy="true|extra|false"
        inverse="true|false"
        cascade="all|none|save-update|delete|all-delete-orphan|delete-orphan"
        sort="unsorted|natural|comparatorClass"
        order-by="column_name asc|desc"
        where="arbitrary sql where condition"
        fetch="join|select|subselect"
        batch-size="N"
        access="field|property|ClassName"
        optimistic-lock="true|false"
        mutable="true|false"
        node="element-name|."
        embed-xml="true|false"
    >

        <key .... />
        <map-key .... />
        <element .... />
    </map>

| `name` : le nom de la propriété contenant la collection
| `table` (optionnel - par défaut = nom de la propriété) : le nom de la table de la collection (non utilisé pour les associations un-à-plusieurs)
| `schema` (optionnel) : le nom du schéma pour surcharger le schéma déclaré dans l'élément racine
| `lazy` (optionnel - par défaut = `true`) : peut être utilisé pour désactiver l'initialisation tardive et spécifier que l'association est toujours rapportée, ou pour activer la récupération extra-paresseuse (extra-lazy) où la plupart des opérations n'initialisent pas la collection (approprié pour de très grosses collections).
| `inverse` (optionnel - par défaut = `false`) : définit cette collection comme l'extrémité "inverse" de l'association bidirectionnelle.
| `cascade` (optionnel - par défaut = `none`) : active les opérations de cascade vers les entités filles.
| `sort` (optionnel) : spécifie une collection triée via un ordre de tri `naturel`, ou via une classe comparateur donnée.
| `order-by` (optional): specifies a table column or columns that define the iteration order of the `Map`, `Set` or bag, together with an optional `asc` or `desc`.
| `where` (optionnel) : spécifie une condition SQL arbitraire `WHERE` à utiliser au chargement ou à la suppression d'une collection (utile si la collection ne doit contenir qu'un sous ensemble des données disponibles).
| `fetch` (optionnel, par défaut = `select`) : à choisir entre récupération par jointures externes, récupération par selects séquentiels, et récupération par sous-selects séquentiels.
| `batch-size` (optionnel, par défaut = `1`) : une "taille de batch" utilisée pour charger plusieurs instances de cette collection.
| `access` (optionnel - par défaut = `property`) : la stratégie que Hibernate doit utiliser pour accéder à la valeur de la propriété.
| `optimistic-lock` (optionnel - par défaut = `true`) : spécifie que changer l'état des résultats de la collection entraîne l'incrémentation de la version appartenant à l'entité (Pour une association un-à-plusieurs, il est souvent raisonnable de désactiver ce paramètre).
| `mutable` (optionnel - par défaut = `true`) : une valeur à `false` spécifie que les éléments de la collection ne changent jamais (une optimisation mineure dans certains cas).

After exploring the basic mapping of collections in the preceding paragraphs we will now focus details like physical mapping considerations, indexed collections and collections of value types.

### 7.2.1. Les clés étrangères d'une collection

On the database level collection instances are distinguished by the foreign key of the entity that owns the collection. This foreign key is referred to as the collection key column, or columns, of the collection table. The collection key column is mapped by the `@JoinColumn` annotation respectively the `<key>` XML element.

There can be a nullability constraint on the foreign key column. For most collections, this is implied. For unidirectional one-to-many associations, the foreign key column is nullable by default, so you may need to specify

    @JoinColumn(nullable=false)

or

    <key column="productSerialNumber" not-null="true"/>

The foreign key constraint can use `ON DELETE CASCADE`. In XML this can be expressed via:

    <key column="productSerialNumber" on-delete="cascade"/>

In annotations the Hibernate specific annotation @OnDelete has to be used.

    @OnDelete(action=OnDeleteAction.CASCADE)

See Section 5.1.11.3, « Key » for more information about the `<key>` element.

### 7.2.2. Collections indexées

In the following paragraphs we have a closer at the indexed collections `List` and `Map` how the their index can be mapped in Hibernate.

#### 7.2.2.1. Lists

Lists can be mapped in two different ways:

- as ordered lists, where the order is not materialized in the database

- as indexed lists, where the order is materialized in the database

To order lists in memory, add `@javax.persistence.OrderBy` to your property. This annotation takes as parameter a list of comma separated properties (of the target entity) and orders the collection accordingly (eg `firstname asc, age desc`), if the string is empty, the collection will be ordered by the primary key of the target entity.

Exemple 7.7. Ordered lists using `@OrderBy`

    @Entity

    public class Customer {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       @OneToMany(mappedBy="customer")

       @OrderBy("number")

       public List<Order> getOrders() { return orders; }

       public void setOrders(List<Order> orders) { this.orders = orders; }

       private List<Order> orders;

    }

    @Entity

    public class Order {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       public String getNumber() { return number; }

       public void setNumber(String number) { this.number = number; }

       private String number;

       @ManyToOne

       public Customer getCustomer() { return customer; }

       public void setCustomer(Customer customer) { this.customer = customer; }

       private Customer number;

    }

    -- Table schema

    |-------------| |----------|

    | Order       | | Customer |

    |-------------| |----------|

    | id          | | id       |

    | number      | |----------|

    | customer_id |

    |-------------|

To store the index value in a dedicated column, use the `@javax.persistence.OrderColumn` annotation on your property. This annotations describes the column name and attributes of the column keeping the index value. This column is hosted on the table containing the association foreign key. If the column name is not specified, the default is the name of the referencing property, followed by underscore, followed by `ORDER` (in the following example, it would be `orders_ORDER`).

Exemple 7.8. Explicit index column using `@OrderColumn`

    @Entity

    public class Customer {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       @OneToMany(mappedBy="customer")

       @OrderColumn(name="orders_index")

       public List<Order> getOrders() { return orders; }

       public void setOrders(List<Order> orders) { this.orders = orders; }

       private List<Order> orders;

    }

    @Entity

    public class Order {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       public String getNumber() { return number; }

       public void setNumber(String number) { this.number = number; }

       private String number;

       @ManyToOne

       public Customer getCustomer() { return customer; }

       public void setCustomer(Customer customer) { this.customer = customer; }

       private Customer number;

    }

    -- Table schema

    |--------------| |----------|

    | Order        | | Customer |

    |--------------| |----------|

    | id           | | id       |

    | number       | |----------|

    | customer_id  |

    | orders_order |

    |--------------|

## Note

We recommend you to convert `the legacy @org.hibernate.annotations.IndexColumn` usages to `@OrderColumn` unless you are making use of the base property. The `base` property lets you define the index value of the first element (aka as base index). The usual value is `0` or `1`. The default is 0 like in Java.

Looking again at the Hibernate mapping file equivalent, the index of an array or list is always of type `integer` and is mapped using the `<list-index>` element. The mapped column contains sequential integers that are numbered from zero by default.

Exemple 7.9. index-list element for indexed collections in xml mapping

    <list-index
            column="column_name"
            base="0|1|..."/>

| `column_name` (champ requis): lenom de la lolonne qui contient les valeurs 'index' de la collection.
| `base` (optionnel - par défaut = `0`) : la valeur de la colonne 'index' qui correspond au premier élément de la liste ou de la table.

Si votre table n'a pas de colonne d'index, et que vous souhaitez tout de même utiliser `List` comme type de propriété, vous devriez mapper la propriété comme un <bag> Hibernate. Un sac (bag) ne garde pas son ordre quand il est récupéré de la base de données, mais il peut être optionnellement trié ou ordonné.

#### 7.2.2.2. Maps

The question with `Map`s is where the key value is stored. There are everal options. Maps can borrow their keys from one of the associated entity properties or have dedicated columns to store an explicit key.

To use one of the target entity property as a key of the map, use `@MapKey(name="myProperty")`, where `myProperty` is a property name in the target entity. When using `@MapKey` without the name attribuate, the target entity primary key is used. The map key uses the same column as the property pointed out. There is no additional column defined to hold the map key, because the map key represent a target property. Be aware that once loaded, the key is no longer kept in sync with the property. In other words, if you change the property value, the key will not change automatically in your Java model.

Exemple 7.10. Use of target entity property as map key via `@MapKey`

    @Entity

    public class Customer {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       @OneToMany(mappedBy="customer")

       @MapKey(name="number")

       public Map<String,Order> getOrders() { return orders; }

       public void setOrders(Map<String,Order> order) { this.orders = orders; }

       private Map<String,Order> orders;

    }

    @Entity

    public class Order {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       public String getNumber() { return number; }

       public void setNumber(String number) { this.number = number; }

       private String number;

       @ManyToOne

       public Customer getCustomer() { return customer; }

       public void setCustomer(Customer customer) { this.customer = customer; }

       private Customer number;

    }

    -- Table schema

    |-------------| |----------|

    | Order       | | Customer |

    |-------------| |----------|

    | id          | | id       |

    | number      | |----------|

    | customer_id |

    |-------------|

Alternatively the map key is mapped to a dedicated column or columns. In order to customize the mapping use one of the following annotations:

- @`MapKeyColumn` if the map key is a basic type. If you don't specify the column name, the name of the property followed by underscore followed by `KEY` is used (for example `orders_KEY`).

- `@MapKeyEnumerated` / `@MapKeyTemporal` if the map key type is respectively an enum or a `Date`.

- `@MapKeyJoinColumn`/`@MapKeyJoinColumns` if the map key type is another entity.

- `@AttributeOverride`/`@AttributeOverrides` when the map key is a embeddable object. Use `key.` as a prefix for your embeddable object property names.

You can also use `@MapKeyClass` to define the type of the key if you don't use generics.

Exemple 7.11. Map key as basic type using `@MapKeyColumn`

    @Entity

    public class Customer {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       @OneToMany @JoinTable(name="Cust_Order")

       @MapKeyColumn(name="orders_number")

       public Map<String,Order> getOrders() { return orders; }

       public void setOrders(Map<String,Order> orders) { this.orders = orders; }

       private Map<String,Order> orders;

    }

    @Entity

    public class Order {

       @Id @GeneratedValue public Integer getId() { return id; }

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

       private Integer id;

       public String getNumber() { return number; }

       public void setNumber(String number) { this.number = number; }

       private String number;

       @ManyToOne

       public Customer getCustomer() { return customer; }

       public void setCustomer(Customer customer) { this.customer = customer; }

       private Customer number;

    }

    -- Table schema

    |-------------| |----------| |---------------|

    | Order       | | Customer | | Cust_Order    |

    |-------------| |----------| |---------------|

    | id          | | id       | | customer_id   |

    | number      | |----------| | order_id      |

    | customer_id |              | orders_number |

    |-------------|              |---------------|

## Note

We recommend you to migrate from `@org.hibernate.annotations.MapKey` / `@org.hibernate.annotation.MapKeyManyToMany` to the new standard approach described above

Using Hibernate mapping files there exists equivalent concepts to the descibed annotations. You have to use `<map-key>`, `<map-key-many-to-many>` and `<composite-map-key>`. `<map-key>` is used for any basic type, `<map-key-many-to-many>` for an entity reference and `<composite-map-key>` for a composite type.

Exemple 7.12. map-key xml mapping element

    <map-key
            column="column_name"
            formula="any SQL expression"
            type="type_name"
            node="@attribute-name"
            length="N"/>

| `colonne` (optionnel) : le nom de la colonne qui contient les valeurs 'index' de la collection.
| `formula` (optionnel): formule SQL utilisée pour évaluer la clé de la mappe.
| `type` (requis) : le type de clés de mappe.

Exemple 7.13. map-key-many-to-many

    <map-key-many-to-many
            column="column_name"
            formula="any SQL expression"
            class="ClassName"
    />

| `colonne` (optionnel) : le nom de la colonne de clés étrangères pour la collection de valeurs 'index'.
| `formula` (optionnel): formule SQ utilisée pour évaluer la clé étrangère d'une clé de mappe.
| `class` (requis) : le nom de la classe utilisée en tant que clé de mappe.

### 7.2.3. Collections of basic types and embeddable objects

In some situations you don't need to associate two entities but simply create a collection of basic types or embeddable objects. Use the `@ElementCollection` for this case.

Exemple 7.14. Collection of basic types mapped via `@ElementCollection`

    @Entity

    public class User {

       [...]

       public String getLastname() { ...}

       @ElementCollection

       @CollectionTable(name="Nicknames", joinColumns=@JoinColumn(name="user_id"))

       @Column(name="nickname")

       public Set<String> getNicknames() { ... }

    }

The collection table holding the collection data is set using the `@CollectionTable` annotation. If omitted the collection table name defaults to the concatenation of the name of the containing entity and the name of the collection attribute, separated by an underscore. In our example, it would be `User_nicknames`.

The column holding the basic type is set using the `@Column` annotation. If omitted, the column name defaults to the property name: in our example, it would be `nicknames`.

But you are not limited to basic types, the collection type can be any embeddable object. To override the columns of the embeddable object in the collection table, use the `@AttributeOverride` annotation.

Exemple 7.15. @ElementCollection for embeddable objects

    @Entity

    public class User {

       [...]

       public String getLastname() { ...}

       @ElementCollection

       @CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id"))

       @AttributeOverrides({

          @AttributeOverride(name="street1", column=@Column(name="fld_street"))

       })

       public Set<Address> getAddresses() { ... }

    }

    @Embeddable

    public class Address {

       public String getStreet1() {...}

       [...]

    }

Such an embeddable object cannot contains a collection itself.

## Note

in `@AttributeOverride`, you must use the `value.` prefix to override properties of the embeddable object used in the map value and the `key.` prefix to override properties of the embeddable object used in the map key.

    @Entity

    public class User {

       @ElementCollection

       @AttributeOverrides({

          @AttributeOverride(name="key.street1", column=@Column(name="fld_street")),

          @AttributeOverride(name="value.stars", column=@Column(name="fld_note"))

       })

       public Map<Address,Rating> getFavHomes() { ... }

## Note

We recommend you to migrate from `@org.hibernate.annotations.CollectionOfElements` to the new `@ElementCollection` annotation.

Using the mapping file approach a collection of values is mapped using the `<element>` tag. For example:

Exemple 7.16. <element> tag for collection values using mapping files

    <element
            column="column_name"
            formula="any SQL expression"
            type="typename"
            length="L"
            precision="P"
            scale="S"
            not-null="true|false"
            unique="true|false"
            node="element-name"
    />

| `colonne` (optionnel) : le nom de la colonne qui contient les valeurs des éléments de collection.
| `formula` (optionnel) : formule SQL utilisée pour évaluer l'élément.
| `type` (requis) : le type d'élément de collection.

## 7.3. Mappages de collection avancés

### 7.3.1. Collections triées

Hibernate supports collections implementing `java.util.SortedMap` and `java.util.SortedSet`. With annotations you declare a sort comparator using `@Sort`. You chose between the comparator types unsorted, natural or custom. If you want to use your own comparator implementation, you'll also have to specify the implementation class using the `comparator` attribute. Note that you need to use either a `SortedSet` or a `SortedMap` interface.

Exemple 7.17. Sorted collection with @Sort

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)

    @JoinColumn(name="CUST_ID")

    @Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class)

    public SortedSet<Ticket> getTickets() {

        return tickets;

    }

Using Hibernate mapping files you specify a comparator in the mapping file with `<sort>`:

Exemple 7.18. Sorted collection using xml mapping

    <set name="aliases"

                table="person_aliases"

                sort="natural">

        <key column="person"/>

        <element column="name" type="string"/>

    </set>

    <map name="holidays" sort="my.custom.HolidayComparator">

        <key column="year_id"/>

        <map-key column="hol_name" type="string"/>

        <element column="hol_date" type="date"/>

    </map>

Les valeurs permises pour l'attribut `sort` sont `unsorted`, `natural` et le nom d'une classe implémentant `java.util.Comparator`.

## Astuce

Les collections triées se comportent réellement comme `java.util.TreeSet` ou `java.util.TreeMap`.

If you want the database itself to order the collection elements, use the `order-by` attribute of `set`, `bag` or `map` mappings. This solution is implemented using `LinkedHashSet` or `LinkedHashMap` and performs the ordering in the SQL query and not in the memory.

Exemple 7.19. Sorting in database using order-by

    <set name="aliases" table="person_aliases" order-by="lower(name) asc">

        <key column="person"/>

        <element column="name" type="string"/>

    </set>

    <map name="holidays" order-by="hol_date, hol_name">

        <key column="year_id"/>

        <map-key column="hol_name" type="string"/>

        <element column="hol_date type="date"/>

    </map>

## Note

Notez que la valeur de l'attribut `order-by` est un ordre SQL, et non pas un ordre HQL.

Les associations peuvent même être triées sur des critères arbitraires à l'exécution en utilisant un `filter()` de collection :

Exemple 7.20. Sorting via a query filter

    sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

### 7.3.2. Associations bidirectionnelles

Une association bidirectionnelle permet la navigation à partir des deux extrémités de l'association. Deux types d'associations bidirectionnelles sont supportées :

un-à-plusieurs (one-to-many)

  ensemble ou sac à une extrémité, une seule valeur à l'autre

plusieurs-à-plusieurs

  ensemble ou sac aux deux extrémités

Often there exists a many to one association which is the owner side of a bidirectional relationship. The corresponding one to many association is in this case annotated by `@OneToMany(mappedBy=...)`

Exemple 7.21. Bidirectional one to many with many to one side as association owner

    @Entity

    public class Troop {

        @OneToMany(mappedBy="troop")

        public Set<Soldier> getSoldiers() {

        ...

    }

    @Entity

    public class Soldier {

        @ManyToOne

        @JoinColumn(name="troop_fk")

        public Troop getTroop() {

        ...

    }

`Troop` has a bidirectional one to many relationship with `Soldier` through the `troop` property. You don't have to (must not) define any physical mapping in the `mappedBy` side.

To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the `mappedBy` element and set the many to one `@JoinColumn` as insertable and updatable to false. This solution is not optimized and will produce additional UPDATE statements.

Exemple 7.22. Bidirectional associtaion with one to many side as owner

    @Entity

    public class Troop {

        @OneToMany

        @JoinColumn(name="troop_fk") //we need to duplicate the physical information

        public Set<Soldier> getSoldiers() {

        ...

    }

    @Entity

    public class Soldier {

        @ManyToOne

        @JoinColumn(name="troop_fk", insertable=false, updatable=false)

        public Troop getTroop() {

        ...

    }

How does the mappping of a bidirectional mapping look like in Hibernate mapping xml? There you define a bidirectional one-to-many association by mapping a one-to-many association to the same table column(s) as a many-to-one association and declaring the many-valued end `inverse="true"`.

Exemple 7.23. Bidirectional one to many via Hibernate mapping files

    <class name="Parent">

        <id name="id" column="parent_id"/>

        ....

        <set name="children" inverse="true">

            <key column="parent_id"/>

            <one-to-many class="Child"/>

        </set>

    </class>

    <class name="Child">

        <id name="id" column="child_id"/>

        ....

        <many-to-one name="parent"

            class="Parent"

            column="parent_id"

            not-null="true"/>

    </class>

Mapper une extrémité d'une association avec `inverse="true"` n'affecte pas l'opération de cascades, ce sont des concepts orthogonaux.

A many-to-many association is defined logically using the `@ManyToMany` annotation. You also have to describe the association table and the join conditions using the `@JoinTable` annotation. If the association is bidirectional, one side has to be the owner and one side has to be the inverse end (ie. it will be ignored when updating the relationship values in the association table):

Exemple 7.24. Many to many association via @ManyToMany

    @Entity

    public class Employer implements Serializable {

        @ManyToMany(

            targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,

            cascade={CascadeType.PERSIST, CascadeType.MERGE}

        )

        @JoinTable(

            name="EMPLOYER_EMPLOYEE",

            joinColumns=@JoinColumn(name="EMPER_ID"),

            inverseJoinColumns=@JoinColumn(name="EMPEE_ID")

        )

        public Collection getEmployees() {

            return employees;

        }

        ...

    }

    @Entity

    public class Employee implements Serializable {

        @ManyToMany(

            cascade = {CascadeType.PERSIST, CascadeType.MERGE},

            mappedBy = "employees",

            targetEntity = Employer.class

        )

        public Collection getEmployers() {

            return employers;

        }

    }

In this example `@JoinTable` defines a `name`, an array of join columns, and an array of inverse join columns. The latter ones are the columns of the association table which refer to the `Employee` primary key (the "other side"). As seen previously, the other side don't have to (must not) describe the physical mapping: a simple `mappedBy` argument containing the owner side property name bind the two.

As any other annotations, most values are guessed in a many to many relationship. Without describing any physical mapping in a unidirectional many to many the following rules applied. The table name is the concatenation of the owner table name, _ and the other side table name. The foreign key name(s) referencing the owner table is the concatenation of the owner table name, _ and the owner primary key column(s). The foreign key name(s) referencing the other side is the concatenation of the owner property name, _, and the other side primary key column(s). These are the same rules used for a unidirectional one to many relationship.

Exemple 7.25. Default values for `@ManyToMany` (uni-directional)

    @Entity

    public class Store {

        @ManyToMany(cascade = CascadeType.PERSIST)

        public Set<City> getImplantedIn() {

            ...

        }

    }

    @Entity

    public class City {

        ... //no bidirectional relationship

    }

A `Store_City` is used as the join table. The `Store_id` column is a foreign key to the `Store` table. The `implantedIn_id` column is a foreign key to the `City` table.

Without describing any physical mapping in a bidirectional many to many the following rules applied. The table name is the concatenation of the owner table name, _ and the other side table name. The foreign key name(s) referencing the owner table is the concatenation of the other side property name, _, and the owner primary key column(s). The foreign key name(s) referencing the other side is the concatenation of the owner property name, _, and the other side primary key column(s). These are the same rules used for a unidirectional one to many relationship.

Exemple 7.26. Default values for `@ManyToMany` (bi-directional)

    @Entity

    public class Store {

        @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})

        public Set<Customer> getCustomers() {

            ...

        }

    }

    @Entity

    public class Customer {

        @ManyToMany(mappedBy="customers")

        public Set<Store> getStores() {

            ...

        }

    }

A `Store_Customer` is used as the join table. The `stores_id` column is a foreign key to the `Store` table. The `customers_id` column is a foreign key to the `Customer` table.

Using Hibernate mapping files you can map a bidirectional many-to-many association by mapping two many-to-many associations to the same database table and declaring one end as inverse.

## Note

You cannot select an indexed collection.

Exemple 7.27, « Many to many association using Hibernate mapping files » shows a bidirectional many-to-many association that illustrates how each category can have many items and each item can be in many categories:

Exemple 7.27. Many to many association using Hibernate mapping files

    <class name="Category">

        <id name="id" column="CATEGORY_ID"/>

        ...

        <bag name="items" table="CATEGORY_ITEM">

            <key column="CATEGORY_ID"/>

            <many-to-many class="Item" column="ITEM_ID"/>

        </bag>

    </class>

    <class name="Item">

        <id name="id" column="ITEM_ID"/>

        ...

        <!-- inverse end -->

        <bag name="categories" table="CATEGORY_ITEM" inverse="true">

            <key column="ITEM_ID"/>

            <many-to-many class="Category" column="CATEGORY_ID"/>

        </bag>

    </class>

Les changements faits uniquement sur l'extrémité inverse de l'association ne sont pas persistés. Ceci signifie qu'Hibernate a deux représentations en mémoire pour chaque association bidirectionnelle, un lien de A vers B et un autre de B vers A. Ceci est plus facile à comprendre si vous pensez au modèle objet de Java et à la façon dont nous créons une relation plusieurs-à-plusieurs dans Java :

Exemple 7.28. Effect of inverse vs. non-inverse side of many to many associations

    category.getItems().add(item);           // The category now "knows" about the relationship

    item.getCategories().add(category);      // The item now "knows" about the relationship

    session.persist(item);                   // The relationship won't be saved!

    session.persist(category);               // The relationship will be saved

La partie non-inverse est utilisée pour sauvegarder la représentation en mémoire dans la base de données.

### 7.3.3. Associations bidirectionnelles avec des collections indexées

There are some additional considerations for bidirectional mappings with indexed collections (where one end is represented as a `<list>` or `<map>`) when using Hibernate mapping files. If there is a property of the child class that maps to the index column you can use `inverse="true"` on the collection mapping:

Exemple 7.29. Bidirectional association with indexed collection

    <class name="Parent">

        <id name="id" column="parent_id"/>

        ....

        <map name="children" inverse="true">

            <key column="parent_id"/>

            <map-key column="name"

                type="string"/>

            <one-to-many class="Child"/>

        </map>

    </class>

    <class name="Child">

        <id name="id" column="child_id"/>

        ....

        <property name="name"

            not-null="true"/>

        <many-to-one name="parent"

            class="Parent"

            column="parent_id"

            not-null="true"/>

    </class>

Mais, si il n'y a pas de telle propriété sur la classe enfant, nous ne pouvons pas considérer l'association comme vraiment bidirectionnelle (il y a des informations disponibles à une extrémité de l'association qui ne sont pas disponibles à l'autre extrémité). Dans ce cas, nous ne pouvons pas mapper la collection `inverse="true"`. Par contre, nous utiliserons le mappage suivant :

Exemple 7.30. Bidirectional association with indexed collection, but no index column

    <class name="Parent">

        <id name="id" column="parent_id"/>

        ....

        <map name="children">

            <key column="parent_id"

                not-null="true"/>

            <map-key column="name"

                type="string"/>

            <one-to-many class="Child"/>

        </map>

    </class>

    <class name="Child">

        <id name="id" column="child_id"/>

        ....

        <many-to-one name="parent"

            class="Parent"

            column="parent_id"

            insert="false"

            update="false"

            not-null="true"/>

    </class>

Note that in this mapping, the collection-valued end of the association is responsible for updates to the foreign key.

### 7.3.4. Associations ternaires

Il y a trois approches possibles pour mapper une association ternaire. L'une est d'utiliser une `Map` avec une association comme son index :

Exemple 7.31. Ternary association mapping

    @Entity

    public class Company {

       @Id

       int id;

       ...

       @OneToMany // unidirectional

       @MapKeyJoinColumn(name="employee_id")

       Map<Employee, Contract> contracts;

    }

    // or

    <map name="contracts">

        <key column="employer_id" not-null="true"/>

        <map-key-many-to-many column="employee_id" class="Employee"/>

        <one-to-many class="Contract"/>

    </map>

A second approach is to remodel the association as an entity class. This is the most common approach. A final alternative is to use composite elements, which will be discussed later.

### 7.3.5. `Using an <idbag>`

The majority of the many-to-many associations and collections of values shown previously all map to tables with composite keys, even though it has been suggested that entities should have synthetic identifiers (surrogate keys). A pure association table does not seem to benefit much from a surrogate key, although a collection of composite values might. For this reason Hibernate provides a feature that allows you to map many-to-many associations and collections of values to a table with a surrogate key.

L'élément `<idbag>` vous laisse mapper une `List` (ou une `Collection`) avec une sémantique de sac. Par exemple :

    <idbag name="lovers" table="LOVERS">

        <collection-id column="ID" type="long">

            <generator class="sequence"/>

        </collection-id>

        <key column="PERSON1"/>

        <many-to-many column="PERSON2" class="Person" fetch="join"/>

    </idbag>

Comme vous pouvez le constater, un `<idbag>` a un générateur d'id artificiel, exactement comme une classe d'entité ! Une clé subrogée différente est assignée à chaque ligne de la collection. Cependant, Hibernate ne fournit pas de mécanisme pour découvrir la valeur d'une clé subrogée d'une ligne particulière.

Notez que les performances de la mise à jour d'un `<idbag>` sont bien meilleures qu'un `<bag>` ordinaire ! Hibernate peut localiser des lignes individuelles efficacement et les mettre à jour ou les effacer individuellement, comme une liste, une map ou un ensemble.

Dans l'implémentation actuelle, la stratégie de la génération de l'identifiant `native` n'est pas supportée pour les identifiants de collection `<idbag>`.

## 7.4. Exemples de collections

Exemples de collections

La classe suivante possède une collection d'instances `Child`(filles) :

Exemple 7.32. Example classes `Parent` and `Child`

    public class Parent {

        private long id;

        private Set<Child> children;

        // getter/setter

        ...

    }

    public class Child {

       private long id;

       private String name

       // getter/setter

       ...

    }

Si chaque instance fille a au plus un parent, le mappage le plus naturel est une association un-à-plusieurs :

Exemple 7.33. One to many unidirectional `Parent-Child` relationship using annotations

    public class Parent {

        @Id

        @GeneratedValue

        private long id;

        @OneToMany

        private Set<Child> children;

        // getter/setter

        ...

    }

    public class Child {

       @Id

       @GeneratedValue

       private long id;

       private String name;

       // getter/setter

       ...

    }

Exemple 7.34. One to many unidirectional `Parent-Child` relationship using mapping files

    <hibernate-mapping>

        <class name="Parent">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <set name="children">

                <key column="parent_id"/>

                <one-to-many class="Child"/>

            </set>

        </class>

        <class name="Child">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <property name="name"/>

        </class>

    </hibernate-mapping>

Ceci mappe les définitions de tables suivantes :

Exemple 7.35. Table definitions for unidirectional `Parent`-`Child` relationship

    create table parent ( id bigint not null primary key )

    create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )

    alter table child add constraint childfk0 (parent_id) references parent

Si le parent est requis, utilisez une association bidirectionnelle un-à-plusieurs :

Exemple 7.36. One to many bidirectional `Parent-Child` relationship using annotations

    public class Parent {

        @Id

        @GeneratedValue

        private long id;

        @OneToMany(mappedBy="parent")

        private Set<Child> children;

        // getter/setter

        ...

    }

    public class Child {

       @Id

       @GeneratedValue

       private long id;

       private String name;

       @ManyToOne

       private Parent parent;

       // getter/setter

       ...

    }

Exemple 7.37. One to many bidirectional `Parent-Child` relationship using mapping files

    <hibernate-mapping>

        <class name="Parent">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <set name="children" inverse="true">

                <key column="parent_id"/>

                <one-to-many class="Child"/>

            </set>

        </class>

        <class name="Child">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <property name="name"/>

            <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>

        </class>

    </hibernate-mapping>

Notez la contrainte `NOT NULL` :

Exemple 7.38. Table definitions for bidirectional `Parent`-`Child` relationship

    create table parent ( id bigint not null primary key )

    create table child ( id bigint not null

                         primary key,

                         name varchar(255),

                         parent_id bigint not null )

    alter table child add constraint childfk0 (parent_id) references parent

Alternatively, if this association must be unidirectional you can enforce the `NOT NULL` constraint.

Exemple 7.39. Enforcing NOT NULL constraint in unidirectional relation using annotations

    public class Parent {

        @Id

        @GeneratedValue

        private long id;

        @OneToMany(optional=false)

        private Set<Child> children;

        // getter/setter

        ...

    }

    public class Child {

       @Id

       @GeneratedValue

       private long id;

       private String name;

       // getter/setter

       ...

    }

Exemple 7.40. Enforcing NOT NULL constraint in unidirectional relation using mapping files

    <hibernate-mapping>

        <class name="Parent">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <set name="children">

                <key column="parent_id" not-null="true"/>

                <one-to-many class="Child"/>

            </set>

        </class>

        <class name="Child">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <property name="name"/>

        </class>

    </hibernate-mapping>

On the other hand, if a child has multiple parents, a many-to-many association is appropriate.

Exemple 7.41. Many to many `Parent-Child` relationship using annotations

    public class Parent {

        @Id

        @GeneratedValue

        private long id;

        @ManyToMany

        private Set<Child> children;

        // getter/setter

        ...

    }

    public class Child {

       @Id

       @GeneratedValue

       private long id;

       private String name;

       // getter/setter

       ...

    }

Exemple 7.42. Many to many `Parent-Child` relationship using mapping files

    <hibernate-mapping>

        <class name="Parent">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <set name="children" table="childset">

                <key column="parent_id"/>

                <many-to-many class="Child" column="child_id"/>

            </set>

        </class>

        <class name="Child">

            <id name="id">

                <generator class="sequence"/>

            </id>

            <property name="name"/>

        </class>

    </hibernate-mapping>

Définitions des tables :

Exemple 7.43. Table definitions for many to many releationship

    create table parent ( id bigint not null primary key )
    create table child ( id bigint not null primary key, name varchar(255) )
    create table childset ( parent_id bigint not null,
                            child_id bigint not null,
                            primary key ( parent_id, child_id ) )
    alter table childset add constraint childsetfk0 (parent_id) references parent
    alter table childset add constraint childsetfk1 (child_id) references child

For more examples and a complete explanation of a parent/child relationship mapping, see Chapitre 24, Exemple : père/fils for more information. Even more complex association mappings are covered in the next chapter.

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 6. Types
- Niveau supérieur
- Sommaire
- SuivantChapitre 8. Mapper les associations