6.4. Mapping a parent/children relationship

From our experience with the Hibernate user community, we know that the first thing many developers try to do when they begin using Hibernate is a mapping of a parent/children relationship. This is usually the first time you encounter collections. It's also the first time you have to think about the differences between entities and value types, or get lost in the complexity of ORM.

Managing the associations between classes and the relationships between tables is at the heart of ORM. Most of the difficult problems involved in implementing an ORM solution relate to association management.

You mapped relationships between classes of value type in the previous section and earlier in the book, with varying multiplicity of the relationship ends. You map a one multiplicity with a simple <property> or as a <component>. The many association multiplicity requires a collection of value types, with <element> or <composite-element> mappings.

Now you want to map one- and many-valued relationships between entity classes. Clearly, entity aspects such as shared references and independent lifecycle complicate this relationship mapping. We'll approach these issues step by step; and, in case you aren't familiar with the term multiplicity, we'll also discuss that.

The relationship we show in the following sections is always the same, between the Item and Bid entity classes, as can be seen in figure 6.8.

Memorize this class diagram. But first, there's something we need to explain up front.

If you've used EJB CMP 2.0, you're familiar with the concept of a managed association (or managed relationship). CMP associations are called container managed relationships (CMRs) for a reason. Associations in CMP are inherently bidirectional. A change made to one side of an association is instantly reflected at the other side. For example, if you call aBid.setItem(anItem), the container automatically calls anItem.getBids().add(aBid).

Figure 6-8. Relationship between Item and Bid

POJO-oriented persistence engines such as Hibernate don't implement managed associations, and POJO standards such as EJB 3.0 and Java Persistence don't require managed associations. Contrary to EJB 2.0 CMR, Hibernate and JPA associations are all inherently unidirectional. As far as Hibernate is concerned, the association from Bid to Item is a different association than the association from Item to Bid! This is a good thing—otherwise your entity classes wouldn't be usable outside of a runtime container (CMR was a major reason why EJB 2.1 entities were considered problematic).

Because associations are so important, you need a precise language for classifying them.

6.4.1. Multiplicity

In describing and classifying associations, we'll almost always use the term multiplicity. In our example, the multiplicity is just two bits of information:

  • Can there be more than one Bid for a particular Item?

  • Can there be more than one Item for a particular Bid?

After glancing at the domain model (see figure 6.8), you can conclude that the association from Bid to Item is a many-to-one association. Recalling that associations are directional, you classify the inverse association from Item to Bid as a one-to-many association.

There are only two more possibilities: many-to-many and one-to-one. We'll get back to these in the next chapter.

In the context of object persistence, we aren't interested in whether many means two or a maximum of five or unrestricted. And we're only barely interested in optionality of most associations; we don't especially care whether an associated instance is required or if the other end in an association can be NULL (meaning zero-to-many and to-zero association) However, these are important aspects in your relational data schema that influence your choice of integrity rules and the constraints you define in SQL DDL (see chapter 8, section 8.3, "Improving schema DDL").

6.4.2. The simplest possible association

The association from Bid to Item (and vice versa) is an example of the simplest possible kind of entity association. You have two properties in two classes. One is a collection of references, and the other a single reference.

First, here's the Java class implementation of Bid:

public class Bid {
    ...

    private Item item;

    public void setItem(Item item) {
        this.item = item;
    }

    public Item getItem() {
        return item;
    }

    ...
}

Next, this is the Hibernate mapping for this association:

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

This mapping is called a unidirectional many-to-one association. (Actually, because it's unidirectional, you don't know what is on the other side, and you could just as well call this mapping a unidirectional to-one association mapping.) The column ITEM_ID in the BID table is a foreign key to the primary key of the ITEM table.

You name the class Item, which is the target of this association, explicitly. This is usually optional, because Hibernate can determine the target type with reflection on the Java property.

You added the not-null attribute because you can't have a bid without an item—a constraint is generated in the SQL DDL to reflect this. The foreign key column ITEM_ID in the BID can never be NULL, the association is not to-zero-or-one. The table structure for this association mapping is shown in figure 6.9.

Figure 6-9. Table relationships and keys for a one-to-many mapping

In JPA, you map this association with the @ManyToOne annotation, either on the field or getter method, depending on the access strategy for the entity (determined by the position of the @Id annotation):

public class Bid {
    ...
    @ManyToOne( targetEntity = auction.model.Item.class )
    @JoinColumn(name = "ITEM_ID", nullable = false)
    private Item item;

    ...
}

There are two optional elements in this mapping. First, you don't have to include the targetEntity of the association; it's implicit from the type of the field. An explicit targetEntity attribute is useful in more complex domain models—for example, when you map a @ManyToOne on a getter method that returns a delegate class, which mimics a particular target entity interface.

The second optional element is the @JoinColumn. If you don't declare the name of the foreign key column, Hibernate automatically uses a combination of the target entity name and the database identifier property name of the target entity. In other words, if you don't add a @JoinColumn annotation, the default name for the foreign key column is item plus id, separated with an underscore. However, because you want to make the foreign key column NOT NULL, you need the annotation anyway to set nullable = false. If you generate the schema with the Hibernate Tools, the optional="false" attribute on the @ManyToOne would also result in a NOT NULL constraint on the generated column.

This was easy. It's critically important to realize that you can write a complete application without using anything else. (Well, maybe a shared primary key one-to-one mapping from time to time, as shown in the next chapter.) You don't need to map the other side of this class association, and you've already mapped everything present in the SQL schema (the foreign key column). If you need the Item instance for which a particular Bid was made, call aBid.getItem(), utilizing the entity association you created. On the other hand, if you need all bids that have been made for an item, you can write a query (in whatever language Hibernate supports).

One of the reasons you use a full object/relational mapping tool like Hibernate is, of course, that you don't want to write that query.

6.4.3. Making the association bidirectional

You want to be able to easily fetch all the bids for a particular item without an explicit query, by navigating and iterating through the network of persistent objects. The most convenient way to do this is with a collection property on Item: anItem.getBids().iterator(). (Note that there are other good reasons to map a collection of entity references, but not many. Always try to think of these kinds of collection mappings as a feature, not a requirement. If it gets too difficult, don't do it.)

You now map a collection of entity references by making the relationship between Item and Bid bidirectional.

First add the property and scaffolding code to the Item class:

public class Item {
    ...

    private Set bids = new HashSet();

    public void setBids(Set bids) {
        this.bids = bids;
    }

    public Set getBids() {
        return bids;
    }

    public void addBid(Bid bid) {
        bid.setItem(this);
        bids.add(bid);
    }
    ...
}

You can think of the code in addBid() (a convenience method) as implementing a managed association in the object model! (We had more to say about these methods in chapter 3, section 3.2, "Implementing the domain model." You may want to review the code examples there.)

A basic mapping for this one-to-many association looks like this:

<class
    name="Item"
    table="ITEM">
    ...

    <set name="bids">
        <key column="ITEM_ID"/>

        <one-to-many class="Bid"/>
    </set>
</class>

If you compare this with the collection mappings earlier in this chapter, you see that you map the content of the collection with a different element, <one-to-many>. This indicates that the collection contains not value type instances, but references to entity instances. Hibernate now knows how to treat shared references and the lifecycle of the associated objects (it disables all the implicit dependent lifecycle of value type instances). Hibernate also knows that the table used for the collection is the same table the target entity class is mapped to—the <set> mapping needs no table attribute.

The column mapping defined by the <key> element is the foreign key column ITEM_ID of the BID table, the same column you already mapped on the other side of the relationship.

Note that the table schema didn't change; it's the same as it was before you mapped the many side of the association. There is, however, one difference: The not null="true" attribute is missing. The problem is that you now have two different unidirectional associations mapped to the same foreign key column. What side controls that column?

At runtime, there are two different in-memory representations of the same foreign key value: the item property of Bid and an element of the bids collection held by an Item. Suppose the application modifies the association, by, for example, adding a bid to an item in this fragment of the addBid() method:

bid.setItem(item);
bids.add(bid);

This code is fine, but in this situation, Hibernate detects two changes to the in-memory persistent instances. From the point of view of the database, only one value has to be updated to reflect these changes: the ITEM_ID column of the BID table.

Hibernate doesn't transparently detect the fact that the two changes refer to the same database column, because at this point you've done nothing to indicate that this is a bidirectional association. In other words, you've mapped the same column twice (it doesn't matter that you did this in two mapping files), and Hibernate always needs to know about this because it can't detect this duplicate automatically (there is no reasonable default way it could be handled).

You need one more thing in the association mapping to make this a real bidirectional association mapping. The inverse attribute tells Hibernate that the collection is a mirror image of the <many-to-one> association on the other side:

<class
    name="Item"
    table="ITEM">
    ...

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

        <key column="ITEM_ID"/>
        <one-to-many class="Bid"/>

    </set>

</class>

Without the inverse attribute, Hibernate tries to execute two different SQL statements, both updating the same foreign key column, when you manipulate the link between two instances. By specifying inverse="true", you explicitly tell Hibernate which end of the link it should not synchronize with the database. In this example, you tell Hibernate that it should propagate changes made at the Bid end of the association to the database, ignoring changes made only to the bids collection.

If you only call anItem.getBids().add(bid), no changes are made persistent! You get what you want only if the other side, aBid.setItem(anItem), is set correctly. This is consistent with the behavior in Java without Hibernate: If an association is bidirectional, you have to create the link with pointers on two sides, not just one. It's the primary reason why we recommend convenience methods such as addBid()—they take care of the bidirectional references in a system without container-managed relationships.

Note that an inverse side of an association mapping is always ignored for the generation of SQL DDL by the Hibernate schema export tools. In this case, the ITEM_ID foreign key column in the BID table gets a NOT NULL constraint, because you've declared it as such in the noninverse <many-to-one> mapping.

(Can you switch the inverse side? The <many-to-one> element doesn't have an inverse attribute, but you can map it with update="false" and insert="false" to effectively ignore it for any UPDATE or INSERT statements. The collection side is then noninverse and considered for insertion or updating of the foreign key column. We'll do this in the next chapter.)

Let's map this inverse collection side again, with JPA annotations:

public class Item {
    ...

    @OneToMany(mappedBy = "item")
    private Set<Bid> bids = new HashSet<Bid>();

    ...
}

The mappedBy attribute is the equivalent of the inverse attribute in XML mappings; however, it has to name the inverse property of the target entity. Note that you don't specify the foreign key column again here (it's mapped by the other side), so this isn't as verbose as the XML.

You now have a working bidirectional many-to-one association (which could also be called a bidirectional one-to-many association). One final option is missing if you want to make it a true parent/children relationship.

6.4.4. Cascading object state

The notion of a parent and a child implies that one takes care of the other. In practice, this means you need fewer lines of code to manage a relationship between a parent and a child, because some things can be taken care of automatically. Let's explore the options.

The following code creates a new Item (which we consider the parent) and a new Bid instance (the child):

Item newItem = new Item();
Bid newBid = new Bid();

newItem.addBid(newBid); // Set both sides of the association

session.save(newItem);
session.save(newBid);

The second call to session.save() seems redundant, if we're talking about a true parent/children relationship. Hold that thought, and think about entities and value types again: If both classes are entities, their instances have a completely independent lifecycle. New objects are transient and have to be made persistent if you want to store them in the database. Their relationship doesn't influence their lifecycle, if they're entities. If Bid would be a value type, the state of a Bid instance is the same as the state of its owning entity. In this case, however, Bid is a separate entity with its own completely independent state. You have three choices:

  • Take care of the independent instances yourself, and execute additional save() and delete() calls on the Bid objects when needed—in addition to the Java code needed to manage the relationship (adding and removing references from collections, and so on).

  • Make the Bid class a value type (a component). You can map the collection with a <composite-element> and get the implicit lifecycle. However, you lose other aspects of an entity, such as possible shared references to an instance.

  • Do you need shared references to Bid objects? Currently, a particular Bid instance isn't referenced by more than one Item. However, imagine that a User entity also has a collection of bids, made by the user. To support shared references, you have to map Bid as an entity. Another reason you need shared references is the successfulBid association from Item in the full CaveatEmptor model. In this case, Hibernate offers transitive persistence, a feature you can enable to save lines of code and to let Hibernate manage the lifecycle of associated entity instances automatically.

You don't want to execute more persistence operations than absolutely necessary, and you don't want to change your domain model—you need shared references to Bid instances. The third option is what you'll use to simplify this parent/children example.

Transitive persistence

When you instantiate a new Bid and add it to an Item, the bid should become persistent automatically. You'd like to avoid making the Bid persistent explicitly with an extra save() operation.

To enable this transitive state across the association, add a cascade option to the XML mapping:

<class
    name="Item"
    table="ITEM">
    ...

    <set name="bids"
         inverse="true"
         cascade="save-update">

        <key column="ITEM_ID"/>
        <one-to-many class="Bid"/>

    </set>

</class>

The cascade="save-update" attribute enables transitive persistence for Bid instances, if a particular Bid is referenced by a persistent Item, in the collection.

The cascade attribute is directional: It applies to only one end of the association. You could also add cascade="save-update" to the <many-to-one> association in the mapping of Bid, but because bids are created after items, doing so doesn't make sense.

JPA also supports cascading entity instance state on associations:

public class Item {
    ...

    @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE },
               mappedBy = "item")
    private Set<Bid> bids = new HashSet<Bid>();
    ...
}

Cascading options are per operation you'd like to be transitive. For native Hibernate, you cascade the save and update operations to associated entities with cascade="save-update". Hibernate's object state management always bundles these two things together, as you'll learn in future chapters. In JPA, the (almost) equivalent operations are persist and merge.

You can now simplify the code that links and saves an Item and a Bid, in native Hibernate:

Item newItem = new Item();
Bid newBid = new Bid();

newItem.addBid(newBid); // Set both sides of the association

session.save(newItem);

All entities in the bids collection are now persistent as well, just as they would be if you called save() on each Bid manually. With the JPA EntityManager API, the equivalent to a Session, the code is as follows:

Item newItem = new Item();
Bid newBid = new Bid();

newItem.addBid(newBid); // Set both sides of the association

entityManager.persist(newItem);

Don't worry about the update and merge operations for now; we'll come back to them later in the book.


FAQ

What is the effect of cascade on inverse? Many new Hibernate users ask this question. The answer is simple: The cascade attribute has nothing to do with the inverse attribute. They often appear on the same collection mapping. If you map a collection of entities as inverse="true", you're controlling the generation of SQL for a bidirectional association mapping. It's a hint that tells Hibernate you mapped the same foreign key column twice. On the other hand, cascading is used as a convenience feature. If you decide to cascade operations from one side of an entity relationship to associated entities, you save the lines of code needed to manage the state of the other side manually. We say that object state becomes transitive. You can cascade state not only on collections of entities, but on all entity association mappings. cascade and inverse have in common the fact that they don't appear on collections of value types or on any other value-type mappings. The rules for these are implied by the nature of value types.

Are you finished now? Well, perhaps not quite.

Cascading deletion

With the previous mapping, the association between Bid and Item is fairly loose. So far, we have only considered making things persistent as a transitive state. What about deletion?

It seems reasonable that deletion of an item implies deletion of all bids for the item. In fact, this is what the composition (the filled out diamond) in the UML diagram means. With the current cascading operations, you have to write the following code to make that happen:

Item anItem = // Load an item

// Delete all the referenced bids
for ( Iterator<Bid> it = anItem.getBids().iterator();
      it.hasNext(); ) {

    Bid bid = it.next();

    it.remove();               // Remove reference from collection
    session.delete(bid);       // Delete it from the database
}

session.delete(anItem);        // Finally, delete the item

First you remove the references to the bids by iterating the collection. You delete each Bid instance in the database. Finally, the Item is deleted. Iterating and removing the references in the collection seems unnecessary; after all, you'll delete the Item at the end anyway. If you can guarantee that no other object (or row in any other table) holds a reference to these bids, you can make the deletion transitive.

Hibernate (and JPA) offer a cascading option for this purpose. You can enable cascading for the delete operation:

<set name="bids"
     inverse="true"
     cascade="save-update, delete">
...

The operation you cascade in JPA is called remove:

public class Item {
    ...

    @OneToMany(cascade = { CascadeType.PERSIST,
                           CascadeType.MERGE,
                           CascadeType.REMOVE },
               mappedBy = "item")
    private Set<Bid> bids = new HashSet<Bid>();

    ...
}

The same code to delete an item and all its bids is reduced to the following, in Hibernate or with JPA:

Item anItem = // Load an item
session.delete(anItem);
entityManager.remove(anItem);

The delete operation is now cascaded to all entities referenced in the collection. You no longer have to worry about removal from the collection and manually deleting those entities one by one.

Let's consider one further complication. You may have shared references to the Bid objects. As suggested earlier, a User may have a collection of references to the Bid instances they made. You can't delete an item and all its bids without removing these references first. You may get an exception if you try to commit this transaction, because a foreign key constraint may be violated.

You have to chase the pointers. This process can get ugly, as you can see in the following code, which removes all references from all users who have references before deleting the bids and finally the item:

Item anItem = // Load an item

// Delete all the referenced bids
for ( Iterator<Bid> it = anItem.getBids().iterator();
      it.hasNext(); ) {

    Bid bid = it.next();

    // Remove references from users who have made this bid
    Query q = session.createQuery(
      "from User u where :bid in elements(u.bids)"
    );
    q.setParameter("bid", bid);
    Collection usersWithThisBid = q.list();

    for (Iterator itUsers = usersWithThisBid.iterator();
         itUsers.hasNext();) {
         User user = (User) itUsers.next();
         user.getBids().remove(bid);
    }
}

session.delete(anItem);
// Finally, delete the item and the associated bids

Obviously, the additional query (in fact, many queries) isn't what you want. However, in a network object model, you don't have any choice other than executing code like this if you want to correctly set pointers and references—there is no persistent garbage collector or other automatic mechanism. No Hibernate cascading option helps you; you have to chase all references to an entity before you finally delete it.

(This isn't the whole truth: Because the BIDDER_ID foreign key column that represents the association from User to Bid is in the BID table, these references are automatically removed at the database level if a row in the BID table is deleted. This doesn't affect any objects that are already present in memory in the current unit of work, and it also doesn't work if BIDDER_ID is mapped to a different (intermediate) table. To make sure all references and foreign key columns are nulled out, you need to chase pointers in Java.)

On the other hand, if you don't have shared references to an entity, you should rethink your mapping and map the bids as a collection components (with the Bid as a <composite-element>). With an <idbag> mapping, even the tables look the same:

<class
    name="Item"
    table="ITEM">
    ...

    <idbag name="bids" table="BID">

        <collection-id type="long" column="BID_ID">
            <generator class="sequence"/>
        </collection-id>

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

        <composite-element class="Bid">
            <parent name="item"/>
            <property .../>
            ...
        </composite-element>

    </idbag>

</class>

The separate mapping for Bid is no longer needed.

If you really want to make this a one-to-many entity association, Hibernate offers another convenience option you may be interested in.

Enabling orphan deletion

The cascading option we explain now is somewhat difficult to understand. If you followed the discussion in the previous section, you should be prepared.

Imagine you want to delete a Bid from the database. Note that you aren't deleting the parent (the Item) in this case. The goal is to remove a row in the BID table. Look at this code:

anItem.getBids().remove(aBid);

If the collection has the Bid mapped as a collection of components, as in the previous section, this code triggers several operations:

  • The aBid instance is removed from the collection Item.bids.

  • Because Bid is mapped as a value type, and no other object can hold a reference to the aBid instance, the row representing this bid is deleted from the BID table by Hibernate.

In other words, Hibernate assumes that aBid is an orphan if it's removed from its owning entity's collection. No other in-memory persistent object is holding a reference to it. No foreign key value that references this row can be present in the database. Obviously, you designed your object model and mapping this way by making the Bid class an embeddable component.

However, what if Bid is mapped as an entity and the collection is a <one-to-many>? The code changes to

anItem.getBids().remove(aBid);
session.delete(aBid);

The aBid instance has its own lifecycle, so it can exist outside of the collection. By deleting it manually, you guarantee that nobody else will hold a reference to it, and the row can be removed safely. You may have removed all other references manually. Or, if you didn't, the database constraints prevent any inconsistency, and you see a foreign key constraint exception.

Hibernate offers you a way to declare this guarantee for collections of entity references. You can tell Hibernate, "If I remove an element from this collection, it will be an entity reference, and it's going to be the only reference to that entity instance. You can safely delete it." The code that worked for deletion with a collection of components works with collections of entity references.

This option is called cascade orphan delete. You can enable it on a collection mapping in XML as follows:

<set name="bids"
     inverse="true"
     cascade="save-update, delete, delete-orphan">
...

With annotations, this feature is available only as a Hibernate extension:

public class Item {
    ...

    @OneToMany(cascade = { CascadeType.PERSIST,
                           CascadeType.MERGE,
                           CascadeType.REMOVE },
               mappedBy = "item")
    @org.hibernate.annotations.Cascade(
        value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN
    )
    private Set<Bid> bids = new HashSet<Bid>();

    ...

}

Also note that this trick works only for collections of entity references in a one-to-many association; conceptually, no other entity association mapping supports it. You should ask yourself at this point, with so many cascading options set on your collection, whether a simple collection of components may be easier to handle. After all, you've enabled a dependent lifecycle for objects referenced in this collection, so you may as well switch to the implicit and fully dependent lifecycle of components.

Finally, let's look at the mapping in a JPA XML descriptor:

<entity-mappings>

    <entity class="auction.model.Item" access="FIELD">
        ...
        <one-to-many name="bids" mapped-by="item">

            <cascade>
                <cascade-persist/>
                <cascade-merge/>
                <cascade-remove/>
            </cascade>
         </one-to-many>
    </entity>

    <entity class="auction.model.Bid" access="FIELD">
        ...
        <many-to-one name="item">
            <join-column name="ITEM_ID"/>
        </many-to-one>
    </entity>

</entity-mappings>

Note that the Hibernate extension for cascade orphan deletion isn't available in this case.

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

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