12.1. Transitive persistence

Real, nontrivial applications work not only with single objects, but rather with networks of objects. When the application manipulates a network of persistent objects, the result may be an object graph consisting of persistent, detached, and transient instances. Transitive persistence is a technique that allows you to propagate persistence to transient and detached subgraphs automatically.

For example, if you add a newly instantiated Category to the already persistent hierarchy of categories, it should become automatically persistent without a call to save() or persist(). We gave a slightly different example in chapter 6, section 6.4, "Mapping a parent/children relationship," when you mapped a parent/child relationship between Bid and Item. In this case, bids were not only automatically made persistent when they were added to an item, but they were also automatically deleted when the owning item was deleted. You effectively made Bid an entity that was completely dependent on another entity, Item (the Bid entity isn't a value type, it still supports shared reference).

There is more than one model for transitive persistence. The best known is persistence by reachability; we discuss it first. Although some basic principles are the same, Hibernate uses its own, more powerful model, as you'll see later. The same is true for Java Persistence, which also has the concept of transitive persistence and almost all the options Hibernate natively provides.

12.1.1. Persistence by reachability

An object persistence layer is said to implement persistence by reachability if any instance becomes persistent whenever the application creates an object reference to the instance from another instance that is already persistent. This behavior is illustrated by the object diagram (note that this isn't a class diagram) in figure 12.1.

In this example, Computer is a persistent object. The objects Desktop PCs and Monitors are also persistent: They're reachable from the Computer Category instance. Electronics and Cellphones are transient. Note that we assume navigation is possible only to child categories, but not to the parent—for example, you can call computer.getChildCategories(). Persistence by reachability is a recursive algorithm. All objects reachable from a persistent instance become persistent either when the original instance is made persistent or just before in-memory state is synchronized with the datastore.

Persistence by reachability guarantees referential integrity; any object graph can be completely re-created by loading the persistent root object. An application may walk the object network from association to association without ever having to worry about the persistent state of the instances. (SQL databases have a different approach to referential integrity, relying on declarative and procedural constraints to detect a misbehaving application.)

In the purest form of persistence by reachability, the database has some top-level or root object, from which all persistent objects are reachable. Ideally, an instance should become transient and be deleted from the database if it isn't reachable via references from the root persistent object.

Figure 12-1. Persistence by reachability with a root persistent object

Neither Hibernate nor other ORM solutions implement this—in fact, there is no analog of the root persistent object in an SQL database and no persistent garbage collector that can detect unreferenced instances. Object-oriented data stores may implement a garbage-collection algorithm, similar to the one implemented for in-memory objects by the JVM. But this option is not available in the ORM world; scanning all tables for unreferenced rows won't perform acceptably.

So, persistence by reachability is at best a halfway solution. It helps you make transient objects persistent and propagate their state to the database without many calls to the persistence manager. However, at least in the context of SQL databases and ORM, it isn't a full solution to the problem of making persistent objects transient (removing their state from the database). This turns out to be a much more difficult problem. You can't remove all reachable instances when you remove an object—other persistent instances may still hold references to them (remember that entities can be shared). You can't even safely remove instances that aren't referenced by any persistent object in memory; the instances in memory are only a small subset of all objects represented in the database.

Let's look at Hibernate's more flexible transitive persistence model.

12.1.2. Applying cascading to associations

Hibernate's transitive persistence model uses the same basic concept as persistence by reachability: Object associations are examined to determine transitive state. Furthermore, Hibernate allows you to specify a cascade style for each association mapping, which offers much more flexibility and fine-grained control for all state transitions. Hibernate reads the declared style and cascades operations to associated objects automatically.

By default, Hibernate doesn't navigate an association when searching for transient or detached objects, so saving, deleting, reattaching, merging, and so on, a Category has no effect on any child category referenced by the childCategories collection of the parent. This is the opposite of the persistence by reachability default behavior. If, for a particular association, you wish to enable transitive persistence, you must override this default in the mapping metadata.

These settings are called cascading options. They're available for every entity association mapping (one-to-one, one-to-many, many-to-many), in XML and annotation syntax. See table 12.1 for a list of all settings and a description of each option.

Table 12-1. Hibernate and Java Persistence entity association cascading options
XML attributeAnnotation Description
None

(Default)

Hibernate ignores the association.

save-update

org.hibernate.annotations.CascadeType.SAVE_UPDATE

Hibernate navigates the association when the Session is flushed and when an object is passed to save() or update(), and saves newly instantiated transient instances and persist changes to detached instances.

persist

javax.persistence.CascadeType.PERSIST

Hibernate makes any associated transient instance persistent when an object is passed to persist(). If you use native Hibernate, cascading occurs only at call-time. If you use the EntityManager module, this operation is cascaded when the persistence context is flushed.

merge

Javax.persistence.CascadeType.MERGE

Hibernate navigates the association and merges the associated detached instances with equivalent persistent instances when an object is passed to merge(). Reachable transient instances are made persistent.

delete

org.hibernate.annotations.CascadeType.DELETE

Hibernate navigates the association and deletes associated persistent instances when an object is passed to delete() or remove().

remove

javax.persistence.CascadeType.REMOVE

This option enables cascading deletion to associated persistent instances when an object is passed to remove() or delete().

lock

org.hibernate.annotations.CascadeType.LOCK

This option cascades the lock() operation to associated instances, reattaching them to the persistence context if the objects are detached. Note that the LockMode isn't cascaded; Hibernate assumes that you don't want pessimistic locks on associated objects—for example, because a pessimistic lock on the root object is good enough to avoid concurrent modification.

replicate

org.hibernate.annotations.CascadeType.REPLICATE

Hibernate navigates the association and cascades the replicate() operation to associated objects.

evict

org.hibernate.annotations.CascadeType.EVICT

Hibernate evicts associated objects from the persistence context when an object is passed to evict() on the Hibernate Session.

refresh

javax.persistence.CascadeType.REFRESH

Hibernate rereads the state of associated objects from the database when an object is passed to refresh().

all

javax.persistence.CascadeType.ALL

This setting includes and enables all cascading options listed previously.

delete- orphan

org.hibernate.annotations.CascadeType.DELETE_ORPHAN

This extra and special setting enables deletion of associated objects when they're removed from the association, that is, from a collection. If you enable this setting on an entity collection, you're telling Hibernate that the associated objects don't have shared references and can be safely deleted when a reference is removed from the collection.


In XML mapping metadata, you put the cascade="..." attribute on <one-to-one> or <many-to-one> mapping element to enable transitive state changes. All collections mappings (<set>, <bag>, <list>, and <map>) support the cascade attribute. The delete-orphan setting, however, is applicable only to collections. Obviously, you never have to enable transitive persistence for a collection that references value-typed classes—here the lifecycle of the associated objects is dependent and implicit. Fine-grained control of dependent lifecycle is relevant and available only for associations between entities.


FAQ

What is the relationship between cascade and inverse? There is no relationship; both are different notions. The noninverse end of an association is used to generate the SQL statements that manage the association in the database (insertion and update of the foreign key column(s)). Cascading enables transitive object state changes across entity class associations.

Here are a few examples of cascading options in XML mapping files. Note that this code isn't from a single entity mapping or a single class, but only illustrative:

<many-to-one name="parent"
             column="PARENT_CATEGORY_ID"
             class="Category"
             cascade="save-update, persist, merge"/>

...

<one-to-one name="shippingAddress"
            class="Address"
            cascade="save-update, lock"/>
...

<set name="bids" cascade="all, delete-orphan"
     inverse="true">
    <key column ="ITEM_ID"/>
    <one-to-many class="Bid"/>
</set>

As you can see, several cascading options can be combined and applied to a particular association as a comma-separated list. Further note that delete-orphan isn't included in all.

Cascading options are declared with annotations in two possible ways. First, all the association mapping annotations, @ManyToOne, @OneToOne, @OneToMany, and @ManyToMany, support a cascade attribute. The value of this attribute is a single or a list of javax.persistence.CascadeType values. For example, the XML illustrative mapping done with annotations looks like this:

@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@JoinColumn(name = "PARENT_CATEGORY_ID", nullable = true)
private Category parent;

...

@OneToMany(cascade = CascadeType.ALL)
private Set<Bid> bids = new HashSet<Bid>();

Obviously, not all cascading types are available in the standard javax.persistence package. Only cascading options relevant for EntityManager operations, such as persist() and merge(), are standardized. You have to use a Hibernate extension annotation to apply any Hibernate-only cascading option:

@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@org.hibernate.annotations.Cascade(
    org.hibernate.annotations.CascadeType.SAVE_UPDATE
)
@JoinColumn(name = "PARENT_CATEGORY_ID", nullable = true)
private Category parent;

...

@OneToOne
@org.hibernate.annotations.Cascade({
    org.hibernate.annotations.CascadeType.SAVE_UPDATE,
    org.hibernate.annotations.CascadeType.LOCK
})
@PrimaryKeyJoinColumn
private Address shippingAddress;

...

@OneToMany(cascade = CascadeType.ALL)
@org.hibernate.annotations.Cascade(
    org.hibernate.annotations.CascadeType.DELETE_ORPHAN
)
private Set<Bid> bids = new HashSet<Bid>();

A Hibernate extension cascading option can be used either as an addition to the options already set on the association annotation (first and last example) or as a stand-alone setting if no standardized option applies (second example).

Hibernate's association-level cascade style model is both richer and less safe than persistence by reachability. Hibernate doesn't make the same strong guarantees of referential integrity that persistence by reachability provides. Instead, Hibernate partially delegates referential integrity concerns to the foreign key constraints of the underlying SQL database.

There is a good reason for this design decision: It allows Hibernate applications to use detached objects efficiently, because you can control reattachment and merging of a detached object graph at the association level. But cascading options aren't available only to avoid unnecessary reattachment or merging: They're useful whenever you need to handle more than one object at a time.

Let's elaborate on the transitive state concept with some example association mappings. We recommend that you read the next section in one turn, because each example builds on the previous one.

12.1.3. Working with transitive state

CaveatEmptor administrators are able to create new categories, rename categories, and move subcategories around in the category hierarchy. This structure can be seen in figure 12.2.

Figure 12-2. Category class with associations to itself

Now, you map this class and the association, using XML:

<class name="Category" table="CATEGORY">
    ...
    <property name="name" column="CATEGORY_NAME"/>

    <many-to-one name="parentCategory"
                 class="Category"
                 column="PARENT_CATEGORY_ID"
                 cascade="none"/>

    <set name="childCategories"
         table="CATEGORY"
         cascade="save-update"
         inverse="true">
        <key column="PARENT_CATEGORY_ID"/>
        <one-to-many class="Category"/>
    </set>
    ...
</class>

This is a recursive bidirectional one-to-many association. The one-valued end is mapped with the <many-to-one> element and the Set typed property with the <set>. Both refer to the same foreign key column PARENT_CATEGORY_ID. All columns are in the same table, CATEGORY.

Figure 12-3. Adding a new Category to the object graph

Creating a new category

Suppose you create a new Category, as a child category of Computer; see figure 12.3.

You have several ways to create this new Laptops object and save it in the database. You can go back to the database and retrieve the Computer category to which the new Laptops category will belong, add the new category, and commit the transaction:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Category computer =
           (Category) session.load(Category.class, computerId);

Category laptops = new Category("Laptops");
computer.getChildCategories().add(laptops);
laptops.setParentCategory(computer);

tx.commit();
session.close();

The computer instance is persistent (note how you use load() to work with a proxy and avoid the database hit), and the childCategories association has cascade-save enabled. Hence, this code results in the new laptops category becoming persistent when tx.commit() is called, as Hibernate cascades the persistent state to the childCategories collection elements of computer. Hibernate examines the state of the objects and their relationships when the persistence context is flushed and queues an INSERT statement.

Creating a new category in a detached fashion

Let's do the same thing again, but this time create the link between Computer and Laptops outside of the persistence context scope:

Category computer =
           (Category) session.get() // Loaded in previous Session

Category laptops = new Category("Laptops");

computer.getChildCategories().add(laptops);
laptops.setParentCategory(computer);

You now have the detached fully initialized (no proxy) computer object, loaded in a previous Session, associated with the new transient laptops object (and vice versa). You make this change to the objects persistent by saving the new object in a second Hibernate Session, a new persistence context:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

// Persist one new category and the link to its parent category
session.save(laptops);

tx.commit();
session.close();

Hibernate inspects the database identifier property of the laptops.parentCategory object and correctly creates the reference to the Computer category in the database. Hibernate inserts the identifier value of the parent into the foreign key field of the new Laptops row in CATEGORY.

You can't obtain a detached proxy for computer in this example, because computer.getChildCategories() would trigger initialization of the proxy and you'd see a LazyInitializationException: The Session is already closed. You can't walk the object graph across uninitialized boundaries in detached state.

Because you have cascade="none" defined for the parentCategory association, Hibernate ignores changes to any of the other categories in the hierarchy (Computer, Electronics)! It doesn't cascade the call to save() to entities referred by this association. If you enabled cascade="save-update" on the <many-to-one> mapping of parentCategory, Hibernate would navigate the whole graph of objects in memory, synchronizing all instances with the database. This is an obvious overhead you'd prefer to avoid.

In this case, you neither need nor want transitive persistence for the parentCategory association.

Saving several new instances with transitive persistence

Why do we have cascading operations? You could save the laptop object, as shown in the previous example, without using any cascade mapping. Well, consider the following case:

Category computer = ... // Loaded in a previous Session

Category laptops = new Category("Laptops");
Category laptopUltraPortable = new Category("Ultra-Portable");
Category laptopTabletPCs = new Category("Tablet PCs");

laptops.addChildCategory(laptopUltraPortable);
laptops.addChildCategory(laptopTabletPCs);

computer.addChildCategory(laptops);

(Notice that the convenience method addChildCategory() sets both ends of the association link in one call, as described earlier in the book.)

It would be undesirable to have to save each of the three new categories individually in a new Session. Fortunately, because you mapped the childCategories association (the collection) with cascade="save-update", you don't need to. The same code shown earlier, which saved the single Laptops category, will save all three new categories in a new Session:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

// Persist all three new Category instances
session.save(laptops);

tx.commit();
session.close();

You're probably wondering why the cascade style is called cascade="save-update" rather then merely cascade="save". Having just made all three categories persistent previously, suppose you make the following changes to the category hierarchy in a subsequent event, outside of a Session (you're working on detached objects again):

laptops.setName("Laptop Computers");                     // Modify
laptopUltraPortable.setName("Ultra-Portable Notebooks"); // Modify
laptopTabletPCs.setName("Tablet Computers");             // Modify

Category laptopBags = new Category("Laptop Bags");
laptops.addChildCategory(laptopBags);                    // Add

You add a new category (laptopBags) as a child of the Laptops category, and modify all three existing categories. The following code propagates all these changes to the database:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

// Update three old Category instances and insert the new one
session.saveOrUpdate(laptops);

tx.commit();
session.close();

Because you specify cascade="save-update" on the childCategories collection, Hibernate determines what is needed to persist the objects to the database. In this case, it queues three SQL UPDATE statements (for laptops, laptopUltraPortable, laptopTablePCs) and one INSERT (for laptopBags). The saveOrUpdate() method tells Hibernate to propagate the state of an instance to the database by creating a new database row if the instance is a new transient instance or updating the existing row if the instance is a detached instance.

More experienced Hibernate users use saveOrUpdate() exclusively; it's much easier to let Hibernate decide what is new and what is old, especially in a more complex network of objects with mixed state. The only (not really serious) disadvantage of exclusive saveOrUpdate() is that it sometimes can't guess whether an instance is old or new without firing a SELECT at the database—for example, when a class is mapped with a natural composite key and no version or timestamp property.

How does Hibernate detect which instances are old and which are new? A range of options is available. Hibernate assumes that an instance is an unsaved transient instance if:

  • The identifier property is null.

  • The version or timestamp property (if it exists) is null.

  • A new instance of the same persistent class, created by Hibernate internally, has the same database identifier value as the given instance.

  • You supply an unsaved-value in the mapping document for the class, and the value of the identifier property matches. The unsaved-value attribute is also available for version and timestamp mapping elements.

  • Entity data with the same identifier value isn't in the second-level cache.

  • You supply an implementation of org.hibernate.Interceptor and return Boolean.TRUE from Interceptor.isUnsaved() after checking the instance in your own code.

In the CaveatEmptor domain model, you use the nullable type java.lang.Long as your identifier property type everywhere. Because you're using generated, synthetic identifiers, this solves the problem. New instances have a null identifier property value, so Hibernate treats them as transient. Detached instances have a nonnull identifier value, so Hibernate treats them accordingly.

It's rarely necessary to customize the automatic detection routines built into Hibernate. The saveOrUpdate() method always knows what to do with the given object (or any reachable objects, if cascading of save-update is enabled for an association). However, if you use a natural composite key and there is no version or timestamp property on your entity, Hibernate has to hit the database with a SELECT to find out if a row with the same composite identifier already exists. In other words, we recommend that you almost always use saveOrUpdate() instead of the individual save() or update() methods, Hibernate is smart enough to do the right thing and it makes transitive "all of this should be in persistent state, no matter if new or old" much easier to handle.

We've now discussed the basic transitive persistence options in Hibernate, for saving new instances and reattaching detached instances with as few lines of code as possible. Most of the other cascading options are equally easy to understand: persist, lock, replicate, and evict do what you would expect—they make a particular Session operation transitive. The merge cascading option has effectively the same consequences as save-update.

It turns out that object deletion is a more difficult thing to grasp; the delete-orphan setting in particular causes confusion for new Hibernate users. This isn't because it's complex, but because many Java developers tend to forget that they're working with a network of pointers.

Considering transitive deletion

Imagine that you want to delete a Category object. You have to pass this object to the delete() method on a Session; it's now in removed state and will be gone from the database when the persistence context is flushed and committed. However, you'll get a foreign key constraint violation if any other Category holds a reference to the deleted row at that time (maybe because it was still referenced as the parent of others).

It's your responsibility to delete all links to a Category before you delete the instance. This is the normal behavior of entities that support shared references. Any value-typed property (or component) value of an entity instance is deleted automatically when the owning entity instance is deleted. Value-typed collection elements (for example, the collection of Image objects for an Item) are deleted if you remove the references from the owning collection.

In certain situations, you want to delete an entity instance by removing a reference from a collection. In other words, you can guarantee that once you remove the reference to this entity from the collection, no other reference will exist. Therefore, Hibernate can delete the entity safely after you've removed that single last reference. Hibernate assumes that an orphaned entity with no references should be deleted. In the example domain model, you enable this special cascading style for the collection (it's only available for collections) of bids, in the mapping of Item:

<set name="bids"
     cascade="all, delete-orphan"
     inverse="true">
    <key column="ITEM_ID"/>
    <one-to-many class="Bid"/>
</set>

You can now delete Bid objects by removing them from this collection—for example, in detached state:

Item anItem = ... // Loaded in previous Session

anItem.getBids().remove(aBid);
anItem.getBids().remove(anotherBid);

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.saveOrUpdate(anItem);

tx.commit();
session.close();

If you don't enable the delete-orphan option, you have to explicitly delete the Bid instances after removing the last reference to them from the collection:

Item anItem = ... // Loaded in previous Session

anItem.getBids().remove(aBid);
anItem.getBids().remove(anotherBid);

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.delete(aBid);
session.delete(anotherBid);

session.saveOrUpdate(anItem);

tx.commit();
session.close();

Automatic deletion of orphans saves you two lines of code—two lines of code that are inconvenient. Without orphan deletion, you'd have to remember all the Bidobjects you wish to delete—the code that removes an element from the collection is often in a different layer than the code that executes the delete() operation. With orphan deletion enabled, you can remove orphans from the collection, and Hibernate will assume that they're no longer referenced by any other entity. Note again that orphan deletion is implicit if you map a collection of components; the extra option is relevant only for a collection of entity references (almost always a <one-to-many>).

Java Persistence and EJB 3.0 also support transitive state changes across entity associations. The standardized cascading options are similar to Hibernate's, so you can learn them easily.

12.1.4. Transitive associations with JPA

The Java Persistence specification supports annotations for entity associations that enable cascading object manipulation. Just as in native Hibernate, each EntityManager operation has an equivalent cascading style. For example, consider the Category tree (parent and children associations) mapped with annotations:

@Entity
public class Category {

    private String name;

    @ManyToOne
    public Category parentCategory;

    @OneToMany(mappedBy = "parentCategory",
               cascade = { CascadeType.PERSIST,
                           CascadeType.MERGE }
               )
    public Set<Category> childCategories = new HashSet<Category>();
    ...
}

You enable standard cascading options for the persist() and merge() operations. You can now create and modify Category instances in persistent or detached state, just as you did earlier with native Hibernate:

Category computer = ... // Loaded in a previous persistence context

Category laptops = new Category("Laptops");
Category laptopUltraPortable = new Category("Ultra-Portable");
Category laptopTabletPCs = new Category("Tablet PCs");

laptops.addChildCategory(laptopUltraPortable);
laptops.addChildCategory(laptopTabletPCs);

computer.setName("Desktops and Laptops");
computer.addChildCategory(laptops);

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

computer = em.merge(computer);

tx.commit();
em.close();

A single call to merge() makes any modification and addition persistent. Remember that merge() isn't the same as reattachment: It returns a new value that you should bind to the current variable as a handle to current state after merging.

Some cascading options aren't standardized, but Hibernate specific. You map these annotations (they're all in the org.hibernate.annotations package) if you're working with the Session API or if you want an extra setting for EntityManager (for example, org.hibernate.annotations.CascadeType.DELETE_ORPHAN). Be careful, though—custom cascading options in an otherwise pure JPA application introduce implicit object state changes that may be difficult to communicate to someone who doesn't expect them.

In the previous sections, we've explored the entity association cascading options with Hibernate XML mapping files and Java Persistence annotations. With transitive state changes, you save lines of code by letting Hibernate navigate and cascade modifications to associated objects. We recommend that you consider which associations in your domain model are candidates for transitive state changes and then implement this with cascading options. In practice, it's extremely helpful if you also write down the cascading option in the UML diagram of your domain model (with stereotypes) or any other similar documentation that is shared between developers. Doing so improves communication in your development team, because everybody knows which operations and associations imply cascading state changes.

Transitive persistence isn't the only way you can manipulate many objects with a single operation. Many applications have to modify large object sets: For example, imagine that you have to set a flag on 50,000 Item objects. This is a bulk operation that is best executed directly in the database.

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

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