7.2. Many-valued entity associations

A many-valued entity association is by definition a collection of entity references. You mapped one of these in the previous chapter, section 6.4, "Mapping a parent/children relationship." A parent entity instance has a collection of references to many child objects—hence, one-to-many.

One-to-many associations are the most important kind of entity association that involves a collection. We go so far as to discourage the use of more exotic association styles when a simple bidirectional many-to-one/one-to-many will do the job. A many-to-many association may always be represented as two many-to-one associations to an intervening class. This model is usually more easily extensible, so we tend not to use many-to-many associations in applications. Also remember that you don't have to map any collection of entities, if you don't want to; you can always write an explicit query instead of direct access through iteration.

If you decide to map collections of entity references, there are a few options and more complex situations that we discuss now, including a many-to-many relationship.

7.2.1. One-to-many associations

The parent/children relationship you mapped earlier was a bidirectional association, with a <one-to-many> and a <many-to-one> mapping. The many end of this association was implemented in Java with a Set; you had a collection of bids in the Item class.

Let's reconsider this mapping and focus on some special cases.

Considering bags

It's possible to use a <bag> mapping instead of a set for a bidirectional one-to-many association. Why would you do this?

Bags have the most efficient performance characteristics of all the collections you can use for a bidirectional one-to-many entity association (in other words, if the collection side is inverse="true"). By default, collections in Hibernate are loaded only when they're accessed for the first time in the application. Because a bag doesn't have to maintain the index of its elements (like a list) or check for duplicate elements (like a set), you can add new elements to the bag without triggering the loading. This is an important feature if you're going to map a possibly large collection of entity references. On the other hand, you can't eager-fetch two collections of bag type simultaneously (for example, if bids and images of an Item were one-to-many bags). We'll come back to fetching strategies in chapter 13, section 13.1, "Defining the global fetch plan." In general we would say that a bag is the best inverse collection for a one-to-many association.

To map a bidirectional one-to-many association as a bag, you have to replace the type of the bids collection in the Item persistent class with a Collection and an ArrayList implementation. The mapping for the association between Item and Bid is left essentially unchanged:

<class name="Bid"
       table="BID">
    ...
    <many-to-one name="item"
                 column="ITEM_ID"
                 class="Item"
                 not-null="true"/>

</class>
<class name="Item"
       table="ITEM">
    ...
    <bag name="bids"
         inverse="true">
        <key column="ITEM_ID"/>
        <one-to-many class="Bid"/>
    </bag>

</class>

You rename the <set> element to <bag>, making no other changes. Even the tables are the same: The BID table has the ITEM_ID foreign key column. In JPA, all Collection and List properties are considered to have bag semantics, so the following is equivalent to the XML mapping:

public class Item {
    ...

    @OneToMany(mappedBy = "item")
    private Collection<Bid> bids = new ArrayList<Bid>();

    ...
}

A bag also allows duplicate elements, which the set you mapped earlier didn't. It turns out that this isn't relevant in this case, because duplicate means you've added a particular reference to the same Bid instance several times. You wouldn't do this in your application code. But even if you add the same reference several times to this collection, Hibernate ignores it—it's mapped inverse.

Unidirectional and bidirectional lists

If you need a real list to hold the position of the elements in a collection, you have to store that position in an additional column. For the one-to-many mapping, this also means you should change the bids property in the Item class to List and initialize the variable with an ArrayList (or keep the Collection interface from the previous section, if you don't want to expose this behavior to a client of the class).

The additional column that holds the position of a reference to a Bid instance is the BID_POSITION, in the mapping of Item:

<class name="Item"
       table="ITEM">
    ...
    <list name="bids">
        <key column="ITEM_ID"/>
        <list-index column="BID_POSITION"/>
        <one-to-many class="Bid"/>
    </list>

</class>

So far this seems straightforward; you've changed the collection mapping to <list> and added the <list-index> column BID_POSITION to the collection table (which in this case is the BID table). Verify this with the table shown in figure 7.6.

This mapping isn't really complete. Consider the ITEM_ID foreign key column: It's NOT NULL (a bid has to reference an item). The first problem is that you don't specify this constraint in the mapping. Also, because this mapping is unidirectional (the collection is noninverse), you have to assume that there is no opposite side mapped to the same foreign key column (where this constraint could be declared). You need to add a not-null="true" attribute to the <key> element of the collection mapping:

Figure 7-6. Storing the position of each bid in the list collection

<class name="Item"
       table="ITEM">
    ...
    <list name="bids">
        <key column="ITEM_ID" not-null="true"/>
        <list-index column="BID_POSITION"/>
        <one-to-many class="Bid"/>
    </list>

</class>

Note that the attribute has to be on the <key> mapping, not on a possible nested <column> element. Whenever you have a noninverse collection of entity references (most of the time a one-to-many with a list, map, or array) and the foreign key join column in the target table is not nullable, you need to tell Hibernate about this. Hibernate needs the hint to order INSERT and UPDATE statements correctly, to avoid a constraint violation.

Let's make this bidirectional with an item property of the Bid. If you follow the examples from earlier chapters, you might want to add a <many-to-one> on the ITEM_ID foreign key column to make this association bidirectional, and enable inverse="true" on the collection. Remember that Hibernate ignores the state of an inverse collection! This time, however, the collection contains information that is needed to update the database correctly: the position of its elements. If only the state of each Bid instance is considered for synchronization, and the collection is inverse and ignored, Hibernate has no value for the BID_POSITION column.

If you map a bidirectional one-to-many entity association with an indexed collection (this is also true for maps and arrays), you have to switch the inverse sides. You can't make an indexed collection inverse="true". The collection becomes responsible for state synchronization, and the one side, the Bid, has to be made inverse. However, there is no inverse="true" for a many-to-one mapping so you need to simulate this attribute on a <many-to-one>:

<class name="Bid"
       table="BID">
    ...
    <many-to-one name="item"
                 column="ITEM_ID"
                 class="Item"
                 not-null="true"
                 insert="false"
                 update="false"/>

</class>

Setting insert and update to false has the desired effect. As we discussed earlier, these two attributes used together make a property effectively read-only. This side of the association is therefore ignored for any write operations, and the state of the collection (including the index of the elements) is the relevant state when the in-memory state is synchronized with the database. You've switched the inverse/noninverse sides of the association, a requirement if you switch from a set or bag to a list (or any other indexed collection).

The equivalent in JPA, an indexed collection in a bidirectional one-to-many mapping, is as follows:

public class Item {
    ...

    @OneToMany
    @JoinColumn(name = "ITEM_ID", nullable = false)
    @org.hibernate.annotations.IndexColumn(name = "BID_POSITION")
    private List<Bid> bids = new ArrayList<Bid>();
    ...

}

This mapping is noninverse because no mappedBy attribute is present. Because JPA doesn't support persistent indexed lists (only ordered with an @OrderBy at load time), you need to add a Hibernate extension annotation for index support. Here's the other side of the association in Bid:

public class Bid {
    ...

    @ManyToOne
    @JoinColumn(name = "ITEM_ID", nullable = false,
                updatable = false, insertable = false)
    private Item item;

    ...

}

We now discuss one more scenario with a one-to-many relationship: an association mapped to an intermediate join table.

Optional one-to-many association with a join table

A useful addition to the Item class is a buyer property. You can then call anItem.getBuyer() to access the User who made the winning bid. (Of course, anItem.getSuccessfulBid().getBidder() can provide the same access with a different path.) If made bidirectional, this association will also help to render a screen that shows all auctions a particular user has won: You call aUser.getBoughtItems() instead of writing a query.

Figure 7-7. Items may be bought by users.

From the point of view of the User class, the association is one-to-many. The classes and their relationship are shown in figure 7.7.

Why is this association different than the one between Item and Bid? The multiplicity 0..* in UML indicates that the reference is optional. This doesn't influence the Java domain model much, but it has consequences for the underlying tables. You expect a BUYER_ID foreign key column in the ITEM table. The column has to be nullable—a particular Item may not have been bought (as long as the auction is still running).

You can accept that the foreign key column can be NULL and apply additional constraints ("allowed to be NULL only if the auction end time hasn't been reached or if no bid has been made"). We always try to avoid nullable columns in a relational database schema. Information that is unknown degrades the quality of the data you store. Tuples represent propositions that are true; you can't assert something you don't know. And, in practice, many developers and DBAs don't create the right constraint and rely on (often buggy) application code to provide data integrity.

An optional entity association, be it one-to-one or one-to-many, is best represented in an SQL database with a join table. See figure 7.8 for an example schema.

You added a join table earlier in this chapter, for a one-to-one association. To guarantee the multiplicity of one-to-one, you applied unique constraints on both foreign key columns of the join table. In the current case, you have a one-to-many multiplicity, so only the ITEM_ID column of the ITEM_BUYER table is unique. A particular item can be bought only once.

Let's map this in XML. First, here's the boughtItems collection of the User class.

Figure 7-8. An optional relationship with a join table avoids nullable foreign key columns.

<set name="boughtItems" table="ITEM_BUYER">
    <key column="USER_ID"/>
    <many-to-many class="Item"
                  column="ITEM_ID"
                  unique="true"/>
</set>

You use a Set as the collection type. The collection table is the join table, ITEM_BUYER; its primary key is a composite of USER_ID and ITEM_ID. The new mapping element you haven't seen before is <many-to-many>; it's required because the regular <one-to-many> doesn't know anything about join tables. By forcing a unique constraint on the foreign key column that references the target entity table, you effectively force a one-to-many multiplicity.

You can map this association bidirectional with the buyer property of Item. Without the join table, you'd add a <many-to-one> with a BUYER_ID foreign key column in the ITEM table. With the join table, you have to move this foreign key column into the join table. This is possible with a <join> mapping:

<join table="ITEM_BUYER"
      optional="true"
      inverse="true">
     <key column="ITEM_ID" unique="true" not-null="true"/>
     <many-to-one name="buyer" column="USER_ID"/>
</join>

Two important details: First, the association is optional, and you tell Hibernate not to insert a row into the join table if the grouped properties (only one here, buyer) are null. Second, this is a bidirectional entity association. As always, one side has to be the inverse end. You've chosen the <join> to be inverse; Hibernate now uses the collection state to synchronize the database and ignores the state of the Item.buyer property. As long as your collection is not an indexed variation (a list, map, or array), you can reverse this by declaring the collection inverse="true". The Java code to create a link between a bought item and a user object is the same in both cases:

aUser.getBoughtItems().add(anItem);
anItem.setBuyer(aUser);

You can map secondary tables in JPA to create a one-to-many association with a join table. First, map a @ManyToOne to a join table:

@Entity
public class Item {
    @ManyToOne
    @JoinTable(
        name = "ITEM_BUYER",
        joinColumns = {@JoinColumn(name = "ITEM_ID")},
        inverseJoinColumns = {@JoinColumn(name = "USER_ID")}
    )
    private User buyer;

    ...
}

At the time of writing, this mapping has the limitation that you can't set it to optional="true"; hence, the USER_ID column is nullable. If you try to add a nullable="false" attribute on the @JoinColumn, Hibernate Annotations thinks that you want the whole buyer property to never be null. Furthermore, the primary key of the join table is now the ITEM_ID column only. This is fine, because you don't want duplicate items in this table—they can be bought only once.

To make this mapping bidirectional, add a collection on the User class and make it inverse with mappedBy:

@OneToMany(mappedBy = "buyer")
private Set<Item> boughtItems = new HashSet<Item>();

We showed a <many-to-many> XML mapping element in the previous section for a one-to-many association on a join table. The @JoinTable annotation is the equivalent in annotations. Let's map a real many-to-many association.

7.2.2. Many-to-many associations

The association between Category and Item is a many-to-many association, as can be seen in figure 7.9.

In a real system, you may not have a many-to-many association. Our experience is that there is almost always other information that must be attached to each link between associated instances (such as the date and time when an item was added to a category) and that the best way to represent this information is via an intermediate association class. In Hibernate, you can map the association class as an entity and map two one-to-many associations for either side. Perhaps more conveniently, you can also map a composite element class, a technique we show later.

Figure 7-9. A many-to-many valued association between Category and Item

It's the purpose of this section to implement a real many-to-many entity association. Let's start with a unidirectional example.

A simple unidirectional many-to-many association

If you require only unidirectional navigation, the mapping is straightforward. Unidirectional many-to-many associations are essentially no more difficult than the collections of value-type instances we discussed earlier. For example, if the Category has a set of Items, you can create this mapping:

<set name="items"
     table="CATEGORY_ITEM"
     cascade="save-update">
    <key column="CATEGORY_ID"/>
    <many-to-many class="Item" column="ITEM_ID"/>
</set>

The join table (or link table, as some developers call it) has two columns: the foreign keys of the CATEGORY and ITEM tables. The primary key is a composite of both columns. The full table structure is shown in figure 7.10.

In JPA annotations, many-to-many associations are mapped with the @ManyToMany attribute:

@ManyToMany
@JoinTable(
    name = "CATEGORY_ITEM",
    joinColumns = {@JoinColumn(name = "CATEGORY_ID")},
    inverseJoinColumns = {@JoinColumn(name = "ITEM_ID")}
)
private Set<Item> items = new HashSet<Item>();

Figure 7-10. Many-to-many entity association mapped to an association table

In Hibernate XML you can also switch to an <idbag> with a separate primary key column on the join table:

<idbag name="items"
       table="CATEGORY_ITEM"
       cascade="save-update">
    <collection-id type="long" column="CATEGORY_ITEM_ID">
        <generator class="sequence"/>
    </collection-id>
    <key column="CATEGORY_ID"/>
    <many-to-many class="Item" column="ITEM_ID"/>
</idbag>

As usual with an <idbag> mapping, the primary key is a surrogate key column, CATEGORY_ITEM_ID. Duplicate links are therefore allowed; the same Item can be added twice to a Category. (This doesn't seem to be a useful feature.) With annotations, you can switch to an identifier bag with the Hibernate @CollectionId:

@ManyToMany
@CollectionId(
    columns = @Column(name = "CATEGORY_ITEM_ID"),
    type = @org.hibernate.annotations.Type(type = "long"),
    generator = "sequence"
)
@JoinTable(
    name = "CATEGORY_ITEM",
    joinColumns = {@JoinColumn(name = "CATEGORY_ID")},
    inverseJoinColumns = {@JoinColumn(name = "ITEM_ID")}
)
private Collection<Item> items = new ArrayList<Item>();

A JPA XML descriptor for a regular many-to-many mapping with a set (you can't use a Hibernate extension for identifier bags) looks like this:

<entity class="auction.model.Category" access="FIELD">
    ...
    <many-to-many name="items">
        <join-table name="CATEGORY_ITEM">
            <join-column name="CATEGORY_ID"/>
            <inverse-join-column name="ITEM_ID"/>
        </join-table>
    </many-to-many>

</entity>

You may even switch to an indexed collection (a map or list) in a many-to-many association. The following example maps a list in Hibernate XML:

<list name="items"
      table="CATEGORY_ITEM"
      cascade="save-update">
    <key column="CATEGORY_ID"/>
    <list-index column="DISPLAY_POSITION"/>
    <many-to-many class="Item" column="ITEM_ID"/>
</list>

The primary key of the link table is a composite of the CATEGORY_ID and DISPLAY_POSITION columns; this mapping guarantees that the position of each Item in a Category is persistent. Or, with annotations:

@ManyToMany
@JoinTable(
    name = "CATEGORY_ITEM",
    joinColumns = {@JoinColumn(name = "CATEGORY_ID")},
    inverseJoinColumns = {@JoinColumn(name = "ITEM_ID")}
)
@org.hibernate.annotations.IndexColumn(name = "DISPLAY_POSITION")
private List<Item> items = new ArrayList<Item>();

As discussed earlier, JPA only supports ordered collections (with an optional @OrderBy annotation or ordered by primary key), so you again have to use a Hibernate extension for indexed collection support. If you don't add an @IndexColumn, the List is stored with bag semantics (no guaranteed persistent order of elements).

Creating a link between a Category and an Item is easy:

aCategory.getItems().add(anItem);

Bidirectional many-to-many associations are slightly more difficult.

A bidirectional many-to-many association

You know that one side in a bidirectional association has to be mapped as inverse because you have named the foreign key column(s) twice. The same principle applies to bidirectional many-to-many associations: Each row of the link table is represented by two collection elements, one element at each end of the association. An association between an Item and a Category is represented in memory by the Item instance in the items collection of the Category, but also by the Category instance in the categories collection of the Item.

Before we discuss the mapping of this bidirectional case, you have to be aware that the code to create the object association also changes:

aCategory.getItems().add(anItem);
anItem.getCategories().add(aCategory);

As always, a bidirectional association (no matter of what multiplicity) requires that you set both ends of the association.

When you map a bidirectional many-to-many association, you must declare one end of the association using inverse="true" to define which side's state is used to update the join table. You can choose which side should be inverse.

Recall this mapping of the items collection from the previous section:

<class name="Category" table="CATEGORY">
    ...
    <set name="items"
         table="CATEGORY_ITEM"
         cascade="save-update">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </set>

You may reuse this mapping for the Category end of the bidirectional association and map the other side as follows:

<class name="Item" table="ITEM">
    ...
    <set name="categories"
         table="CATEGORY_ITEM"
         inverse="true"
         cascade="save-update">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </set>
</class>

Note the inverse="true". Again, this setting tells Hibernate to ignore changes made to the categories collection and that the other end of the association, the items collection, is the representation that should be synchronized with the database if you link instances in Java code.

You have enabled cascade="save-update" for both ends of the collection. This isn't unreasonable, we suppose. On the other hand, the cascading options all, delete, and delete-orphans aren't meaningful for many-to-many associations. (This is good point to test if you understand entities and value types—try to come up with reasonable answers why these cascading options don't make sense for a many-to-many association.)

In JPA and with annotations, making a many-to-many association bidirectional is easy. First, the noninverse side:

@ManyToMany
@JoinTable(
    name = "CATEGORY_ITEM",
    joinColumns = {@JoinColumn(name = "CATEGORY_ID")},
    inverseJoinColumns = {@JoinColumn(name = "ITEM_ID")}
)
private Set<Item> items = new HashSet<Item>();

Now the opposite inverse side:

@ManyToMany(mappedBy = "items")
private Set<Category> categories = new HashSet<Category>();

As you can see, you don't have to repeat the join-table declaration on the inverse side.

What types of collections may be used for bidirectional many-to-many associations? Do you need the same type of collection at each end? It's reasonable to map, for example, a <list> for the noninverse side of the association and a <bag> on the inverse side.

For the inverse end, <set> is acceptable, as is the following bag mapping:

<class name="Item" table="ITEM">
    ...
    <bag name="categories"
         table="CATEGORY_ITEM"
         inverse="true"
         cascade="save-update">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </bag>
</class>

In JPA, a bag is a collection without a persistent index:

@ManyToMany(mappedBy = "items")
private Collection<Category> categories = new ArrayList<Category>();

No other mappings can be used for the inverse end of a many-to-many association. Indexed collections (lists and maps) don't work, because Hibernate won't initialize or maintain the index column if the collection is inverse. In other words, a many-to-many association can't be mapped with indexed collections on both sides.

We already frowned at the use of many-to-many associations, because additional columns on the join table are almost always inevitable.

7.2.3. Adding columns to join tables

In this section, we discuss a question that is asked frequently by Hibernate users: What do I do if my join table has additional columns, not only two foreign key columns?

Imagine that you need to record some information each time you add an Item to a Category. For example, you may need to store the date and the name of the user who added the item to this category. This requires additional columns on the join table, as you can see in figure 7.11.

Figure 7-11. Additional columns on the join table in a many-to-many association

You can use two common strategies to map such a structure to Java classes. The first strategy requires an intermediate entity class for the join table and is mapped with one-to-many associations. The second strategy utilizes a collection of components, with a value-type class for the join table.

Mapping the join table to an intermediate entity

The first option we discuss now resolves the many-to-many relationship between Category and Item with an intermediate entity class, CategorizedItem. Listing 7.1 shows this entity class, which represents the join table in Java, including JPA annotations:

Listing 7-1. An entity class that represents a link table with additional columns
@Entity
@Table(name = "CATEGORIZED_ITEM")
public class CategorizedItem {

    @Embeddable
    public static class Id implements Serializable {

        @Column(name = "CATEGORY_ID")
        private Long categoryId;

        @Column(name = "ITEM_ID")
        private Long itemId;

        public Id() {}

        public Id(Long categoryId, Long itemId) {
            this.categoryId = categoryId;
            this.itemId = itemId;
        }
        public boolean equals(Object o) {
            if (o != null && o instanceof Id) {
                Id that = (Id)o;
                return this.categoryId.equals(that.categoryId) &&
                       this.itemId.equals(that.itemId);
            } else {
                return false;
            }
        }

        public int hashCode() {
            return categoryId.hashCode() + itemId.hashCode();
        }
    }

    @EmbeddedId
    private Id id = new Id();

    @Column(name = "ADDED_BY_USER")
    private String username;

    @Column(name = "ADDED_ON")
    private Date dateAdded = new Date();

    @ManyToOne
    @JoinColumn(name="ITEM_ID",
                insertable = false,
                updatable = false)
    private Item item;

    @ManyToOne
    @JoinColumn(name="CATEGORY_ID",
                insertable = false,
                updatable = false)
    private Category category;

    public CategorizedItem() {}

    public CategorizedItem(String username,
                           Category category,
                           Item item) {
        // Set fields
        this.username = username;

        this.category = category;
        this.item = item;

        // Set identifier values
        this.id.categoryId = category.getId();
        this.id.itemId = item.getId();

        // Guarantee referential integrity
        category.getCategorizedItems().add(this);
        item.getCategorizedItems().add(this);
    }

    // Getter and setter methods
    ...
}

An entity class needs an identifier property. The primary key of the join table is CATEGORY_ID and ITEM_ID, a composite. Hence, the entity class also has a composite key, which you encapsulate in a static nested class for convenience. You can also see that constructing a CategorizedItem involves setting the values of the identifier—composite key values are assigned by the application. Pay extra attention to the constructor and how it sets the field values and guarantees referential integrity by managing collections on either side of the association.

Let's map this class to the join table in XML:

<class name="CategorizedItem"
       table="CATEGORY_ITEM"
       mutable="false">

    <composite-id name="id" class="CategorizedItem$Id">
        <key-property name="categoryId"
                      access="field"
                      column="CATEGORY_ID"/>

        <key-property name="itemId"
                      access="field"
                      column="ITEM_ID"/>
    </composite-id>

    <property name="dateAdded"
              column="ADDED_ON"
              type="timestamp"

              not-null="true"/>

    <property name="username"
              column="ADDED_BY_USER"
              type="string"
              not-null="true"/>

    <many-to-one name="category"
                 column="CATEGORY_ID"
                 not-null="true"
                 insert="false"
                 update="false"/>

    <many-to-one name="item"
                 column="ITEM_ID"
                 not-null="true"
                 insert="false"
                 update="false"/>
</class>

The entity class is mapped as immutable—you'll never update any properties after creation. Hibernate accesses <composite-id> fields directly—you don't need getters and setters in this nested class. The two <many-to-one> mappings are effectively read-only; insert and update are set to false. This is necessary because the columns are mapped twice, once in the composite key (which is responsible for insertion of the values) and again for the many-to-one associations.

The Category and Item entities (can) have a one-to-many association to the CategorizedItem entity, a collection. For example, in Category:

<set name="categorizedItems"
     inverse="true">
    <key column="CATEGORY_ID"/>
    <one-to-many class="CategorizedItem"/>
</set>

And here's the annotation equivalent:

@OneToMany(mappedBy = "category")
private Set<CategorizedItem> categorizedItems =
                                new HashSet<CategorizedItem>();

There is nothing special to consider here; it's a regular bidirectional one-to-many association with an inverse collection. Add the same collection and mapping to Item to complete the association. This code creates and stores a link between a category and an item:

CategorizedItem newLink =
    new CategorizedItem(aUser.getUsername(), aCategory, anItem);

session.save(newLink);

The referential integrity of the Java objects is guaranteed by the constructor of CategorizedItem, which manages the collection in aCategory and in anItem. Remove and delete the link between a category and an item:

aCategory.getCategorizedItems().remove( theLink );
anItem.getCategorizedItems().remove( theLink );

session.delete(theLink);

The primary advantage of this strategy is the possibility for bidirectional navigation: You can get all items in a category by calling aCategory.getCategorizedItems() and the also navigate from the opposite direction with anItem.getCategorizedItems(). A disadvantage is the more complex code needed to manage the CategorizedItem entity instances to create and remove associations—they have to be saved and deleted independently, and you need some infrastructure in the CategorizedItem class, such as the composite identifier. However, you can enable transitive persistence with cascading options on the collections from Category and Item to CategorizedItem, as explained in chapter 12, section 12.1, "Transitive persistence."

The second strategy for dealing with additional columns on the join table doesn't need an intermediate entity class; it's simpler.

Mapping the join table to a collection of components

First, simplify the CategorizedItem class, and make it a value type, without an identifier or any complex constructor:

public class CategorizedItem {
    private String username;
    private Date dateAdded = new Date();
    private Item item;
    private Category category;

    public CategorizedItem(String username,
                           Category category,
                           Item item) {
            this.username = username;
            this.category = category;
            this.item = item;
    }
    ...
    // Getter and setter methods
    // Don't forget the equals/hashCode methods
}

As for all value types, this class has to be owned by an entity. The owner is the Category, and it has a collection of these components:

<class name="Category" table="CATEGORY">

    ...
    <set name="categorizedItems" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <composite-element class="CategorizedItem">
            <parent name="category"/>

            <many-to-one name="item"
                         column="ITEM_ID"
                         not-null="true"
                         class="Item"/>

            <property name="username" column="ADDED_BY_USER"/>
            <property name="dateAdded" column="ADDED_ON"/>

        </composite-element>
     </set>

</class>

This is the complete mapping for a many-to-many association with extra columns on the join table. The <many-to-one> element represents the association to Item; the <property> mappings cover the extra columns on the join table. There is only one change to the database tables: The CATEGORY_ITEM table now has a primary key that is a composite of all columns, not only CATEGORY_ID and ITEM_ID, as in the previous section. Hence, all properties should never be nullable—otherwise you can't identify a row in the join table. Except for this change, the tables still look as shown in figure 7.11.

You can enhance this mapping with a reference to the User instead of just the user's name. This requires an additional USER_ID column on the join table, with a foreign key to USERS. This is a ternary association mapping:

<set name="categorizedItems" table="CATEGORY_ITEM">
    <key column="CATEGORY_ID"/>
    <composite-element class="CategorizedItem">
        <parent name="category"/>

        <many-to-one name="item"
                     column="ITEM_ID"
                     not-null="true"
                     class="Item"/>

        <many-to-one name="user"
                     column="USER_ID"
                     not-null="true"
                     class="User"/>

        <property name="dateAdded" column="ADDED_ON"/>

    </composite-element>
</set>

This is a fairly exotic beast!

The advantage of a collection of components is clearly the implicit lifecycle of the link objects. To create an association between a Category and an Item, add a new CategorizedItem instance to the collection. To break the link, remove the element from the collection. No extra cascading settings are required, and the Java code is simplified:

CategorizedItem aLink =
    new CategorizedItem(aUser.getUserName(), aCategory, anItem);

aCategory.getCategorizedItems().add( aLink );

aCategory.getCategorizedItems().remove( aLink );

The downside of this approach is that there is no way to enable bidirectional navigation: A component (such as CategorizedItem) can't, by definition, have shared references. You can't navigate from Item to CategorizedItem. However, you can write a query to retrieve the objects you need.

Let's do the same mapping with annotations. First, make the component class @Embeddable, and add the component column and association mappings:

@Embeddable
public class CategorizedItem {

    @org.hibernate.annotations.Parent // Optional back-pointer
    private Category category;

    @ManyToOne
    @JoinColumn(name = "ITEM_ID",
                nullable = false,
                updatable = false)
    private Item item;

    @ManyToOne
    @JoinColumn(name = "USER_ID",
                nullable = false,
                updatable = false)
    private User user;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "ADDED_ON", nullable = false, updatable = false)
    private Date dateAdded;

    ...
    // Constructor
    // Getter and setter methods
    // Don't forget the equals/hashCode methods
}

Now map this as a collection of components in the Category class:

@org.hibernate.annotations.CollectionOfElements
@JoinTable(
    name = "CATEGORY_ITEM",
    joinColumns = @JoinColumn(name = "CATEGORY_ID")
)
private Set<CategorizedItem> categorizedItems =
                                new HashSet<CategorizedItem>();

That's it: You've mapped a ternary association with annotations. What looked incredibly complex at the beginning has been reduced to a few lines of annotation metadata, most of it optional.

The last collection mapping we'll explore are Maps of entity references.

7.2.4. Mapping maps

You mapped a Java Map in the last chapter—the keys and values of the Map were value types, simple strings. You can create more complex maps; not only can the keys be references to entities, but so can the values. The result can therefore be a ternary association.

Values as references to entities

First, let's assume that only the value of each map entry is a reference to another entity. The key is a value type, a long. Imagine that the Item entity has a map of Bid instances and that each map entry is a pair of Bid identifier and reference to a Bid instance. If you iterate through anItem.getBidsByIdentifier(), you iterate through map entries that look like (1, <reference to Bid with PK 1>), (2, <reference to Bid with PK 2>), and so on.

The underlying tables for this mapping are nothing special; you again have an ITEM and a BID table, with an ITEM_ID foreign key column in the BID table. Your motivation here is a slightly different representation of the data in the application, with a Map.

In the Item class, include a Map:

@MapKey(name="id")
@OneToMany
private Map<Long,Bid> bidsByIdentifier = new HashMap<Long,Bid>();

New here is the @MapKey element of JPA—it maps a property of the target entity as key of the map.The default if you omit the name attribute is the identifier property of the target entity (so the name here is redundant). Because the keys of a map form a set, values are expected to be unique for a particular map—this is the case for Bid primary keys but likely not for any other property of Bid.

In Hibernate XML, this mapping is as follows:

<map name="bidsByIdentifier">
    <key column="ITEM_ID"/>
    <map-key type="long" formula="BID_ID"/>
    <one-to-many class="Bid"/>
</map>

The formula key for a map makes this column read-only, so it's never updated when you modify the map. A more common situation is a map in the middle of a ternary association.

Ternary associations

You may be a little bored by now, but we promise this is the last time we'll show another way to map the association between Category and Item. Let's summarize what you already know about this many-to-many association:

  • It can be mapped with two collections on either side and a join table that has only two foreign key columns. This is a regular many-to-many association mapping.

  • It can be mapped with an intermediate entity class that represents the join table, and any additional columns therein. A one-to-many association is mapped on either side (Category and Item), and a bidirectional many-to-one equivalent is mapped in the intermediate entity class.

  • It can be mapped unidirectional, with a join table represented as a value type component. The Category entity has a collection of components. Each component has a reference to its owning Category and a many-to-one entity association to an Item. (You can also switch the words Category and Item in this explanation.)

You previously turned the last scenario into a ternary association by adding another many-to-one entity association to a User. Let's do the same with a Map.

A Category has a Map of Item instances—the key of each map entry is a reference to an Item. The value of each map entry is the User who added the Item to the Category. This strategy is appropriate if there are no additional columns on the join table; see the schema in figure 7.12.

The advantage of this strategy is that you don't need any intermediate class, no entity or value type, to represent the ADDED_BY_USER_ID column of the join table in your Java application.

First, here's the Map property in Category with a Hibernate extension annotation.

Figure 7-12. A ternary association with a join table between three entities

@ManyToMany
@org.hibernate.annotations.MapKeyManyToMany(
    joinColumns = @JoinColumn(name = "ITEM_ID")
)
@JoinTable(
    name = "CATEGORY_ITEM",
    joinColumns = @JoinColumn(name = "CATEGORY_ID"),
    inverseJoinColumns = @JoinColumn(name = "USER_ID")
)
private Map<Item,User> itemsAndUser = new HashMap<Item,User>();

The Hibernate XML mapping includes a new element, <map-key-many-to-many>:

<map name="itemsAndUser" table="CATEGORY_ITEM">
    <key column="CATEGORY_ID"/>
    <map-key-many-to-many column="ITEM_ID" class="Item"/>
    <many-to-many column="ADDED_BY_USER_ID" class="User"/>
</map>

To create a link between all three entities, if all your instances are already in persistent state, add a new entry to the map:

aCategory.getItemsAndUser().add( anItem, aUser );

To remove the link, remove the entry from the map. As an exercise, you can try to make this mapping bidirectional, with a collection of categories in Item. Remember that this has to be an inverse collection mapping, so it doesn't support indexed collections.

Now that you know all the association mapping techniques for normal entities, we still have to consider inheritance and associations to the various levels of an inheritance hierarchy. What we really want is polymorphic behavior. Let's see how Hibernate deals with polymorphic entity associations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset