9.3. The Hibernate interfaces

Any transparent persistence tool includes a persistence manager API. This persistence manager usually provides services for the following:

  • Basic CRUD (create, retrieve, update, delete) operations

  • Query execution

  • Control of transactions

  • Management of the persistence context

The persistence manager may be exposed by several different interfaces. In the case of Hibernate, these are Session, Query, Criteria, and Transaction. Under the covers, the implementations of these interfaces are coupled tightly together.

In Java Persistence, the main interface you interact with is the EntityManager; it has the same role as the Hibernate Session. Other Java Persistence interfaces are Query and EntityTransaction (you can probably guess what their counterpart in native Hibernate is).

We'll now show you how to load and store objects with Hibernate and Java Persistence. Sometimes both have exactly the same semantics and API, and even the method names are the same. It's therefore much more important to keep your eyes open for little differences. To make this part of the book easier to understand, we decided to use a different strategy than usual and explain Hibernate first and then Java Persistence.

Let's start with Hibernate, assuming that you write an application that relies on the native API.

9.3.1. Storing and loading objects

In a Hibernate application, you store and load objects by essentially changing their state. You do this in units of work. A single unit of work is a set of operations considered an atomic group. If you're guessing now that this is closely related to transactions, you're right. But, it isn't necessarily the same thing. We have to approach this step by step; for now, consider a unit of work a particular sequence of state changes to your objects that you'd group together.

First you have to begin a unit of work.

Beginning a unit of work

At the beginning of a unit of work, an application obtains an instance of Session from the application's SessionFactory:

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

At this point, a new persistence context is also initialized for you, and it will manage all the objects you work with in that Session. The application may have multiple SessionFactorys if it accesses several databases. How the SessionFactory is created and how you get access to it in your application code depends on your deployment environment and configuration—you should have the simple HibernateUtil startup helper class ready if you followed the setup in "Handling the SessionFactory" in chapter 2, section 2.1.3.

You should never create a new SessionFactory just to service a particular request. Creation of a SessionFactory is extremely expensive. On the other hand, Session creation is extremely inexpensive. The Session doesn't even obtain a JDBC Connection until a connection is required.

The second line in the previous code begins a Transaction on another Hibernate interface. All operations you execute inside a unit of work occur inside a transaction, no matter if you read or write data. However, the Hibernate API is optional, and you may begin a transaction in any way you like—we'll explore these options in the next chapter. If you use the Hibernate Transaction API, your code works in all environments, so you'll do this for all examples in the following sections.

After opening a new Session and persistence context, you use it to load and save objects.

Making an object persistent

The first thing you want to do with a Session is make a new transient object persistent with the save() method (listing 9.2).

Listing 9-2. Making a transient instance persistent
Item item = new Item(); 
item.setName("Playstation3 incl. all accessories"); item.setEndDate( ... ); Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction(); Serializable itemId = session.save(item);
tx.commit();
session.close();

A new transient object item is instantiated as usual ❶. Of course, you may also instantiate it after opening a Session; they aren't related yet. A new Session is opened using the SessionFactory ❷. You start a new transaction.

A call to save() ❸ makes the transient instance of Item persistent. It's now associated with the current Session and its persistence context.

The changes made to persistent objects have to be synchronized with the database at some point. This happens when you commit() the Hibernate Transaction ❹. We say a flush occurs (you can also call flush() manually; more about this later). To synchronize the persistence context, Hibernate obtains a JDBC connection and issues a single SQL INSERT statement. Note that this isn't always true for insertion: Hibernate guarantees that the item object has an assigned database identifier after it has been saved, so an earlier INSERT may be necessary, depending on the identifier generator you have enabled in your mapping. The save() operation also returns the database identifier of the persistent instance.

The Session can finally be closed ❺, and the persistence context ends. The reference item is now a reference to an object in detached state.

You can see the same unit of work and how the object changes state in figure 9.4.

It's better (but not required) to fully initialize the Item instance before managing it with a Session. The SQL INSERT statement contains the values that were held by the object at the point when save() was called. You can modify the object after calling save(), and your changes will be propagated to the database as an (additional) SQL UPDATE.

Figure 9-4. Making an object persistent in a unit of work

Everything between session.beginTransaction() and tx.commit() occurs in one transaction. For now, keep in mind that all database operations in transaction scope either completely succeed or completely fail. If one of the UPDATE or INSERT statements made during flushing on tx.commit() fails, all changes made to persistent objects in this transaction are rolled back at the database level. However, Hibernate doesn't roll back in-memory changes to persistent objects. This is reasonable because a failure of a transaction is normally nonrecoverable, and you have to discard the failed Session immediately. We'll discuss exception handling later in the next chapter.

Retrieving a persistent object

The Session is also used to query the database and retrieve existing persistent objects. Hibernate is especially powerful in this area, as you'll see later in the book. Two special methods are provided for the simplest kind of query: retrieval by identifier. The get() and load() methods are demonstrated in Listing 9.3.

Listing 9-3. Retrieval of a Item by identifier
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Item item = (Item) session.load(Item.class, new Long(1234));
// Item item = (Item) session.get(Item.class, new Long(1234));

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

You can see the same unit of work in figure 9.5.

The retrieved object item is in persistent state and as soon as the persistence context is closed, in detached state.

Figure 9-5. Retrieving a persistent object by identifier

The one difference between get() and load() is how they indicate that the instance could not be found. If no row with the given identifier value exists in the database, get() returns null. The load() method throws an ObjectNotFoundException. It's your choice what error-handling you prefer.

More important, the load() method may return a proxy, a placeholder, without hitting the database. A consequence of this is that you may get an ObjectNotFoundException later, as soon as you try to access the returned placeholder and force its initialization (this is also called lazy loading; we discuss load optimization in later chapters.) The load() method always tries to return a proxy, and only returns an initialized object instance if it's already managed by the current persistence context. In the example shown earlier, no database hit occurs at all! The get() method on the other hand never returns a proxy, it always hits the database.

You may ask why this option is useful—after all, you retrieve an object to access it. It's common to obtain a persistent instance to assign it as a reference to another instance. For example, imagine that you need the item only for a single purpose: to set an association with a Comment: aComment.setForAuction(item). If this is all you plan to do with the item, a proxy will do fine; there is no need to hit the database. In other words, when the Comment is saved, you need the foreign key value of an item inserted into the COMMENT table. The proxy of an Item provides just that: an identifier value wrapped in a placeholder that looks like the real thing.

Modifying a persistent object

Any persistent object returned by get(), load(), or any entity queried is already associated with the current Session and persistence context. It can be modified, and its state is synchronized with the database (see listing 9.4).

Figure 9.6 shows this unit of work and the object transitions.

Listing 9-4. Modifying a persistent instance
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Item item = (Item) session.get(Item.class, new Long(1234));

item.setDescription("This Playstation is as good as new!");

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

Figure 9-6. Modifying a persistent instance

First, you retrieve the object from the database with the given identifier. You modify the object, and these modifications are propagated to the database during flush when tx.commit() is called. This mechanism is called automatic dirty checking—that means Hibernate tracks and saves the changes you make to an object in persistent state. As soon as you close the Session, the instance is considered detached.

Making a persistent object transient

You can easily make a persistent object transient, removing its persistent state from the database, with the delete() method (see listing 9.5).

Listing 9-5. Making a persistent object transient using delete()
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Item item = (Item) session.load(Item.class, new Long(1234));

session.delete(item);

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

Look at figure 9.7.

The item object is in removed state after you call delete(); you shouldn't continue working with it, and, in most cases, you should make sure any reference to it in your application is removed. The SQL DELETE is executed only when the Session's persistence context is synchronized with the database at the end of the unit of work. After the Session is closed, the item object is considered an ordinary transient instance. The transient instance is destroyed by the garbage collector if it's no longer referenced by any other object. Both the in-memory object instance and the persistent database row will have been removed.

Figure 9-7. Making a persistent object transient


FAQ

Do I have to load an object to delete it? Yes, an object has to be loaded into the persistence context; an instance has to be in persistent state to be removed (note that a proxy is good enough). The reason is simple: You may have Hibernate interceptors enabled, and the object must be passed through these interceptors to complete its lifecycle. If you delete rows in the database directly, the interceptor won't run. Having said that, Hibernate (and Java Persistence) offer bulk operations that translate into direct SQL DELETE statements; we'll discuss these operations in chapter 12, section 12.2, "Bulk and batch operations."

Hibernate can also roll back the identifier of any entity that has been deleted, if you enable the hibernate.use_identifier_rollback configuration option. In the previous example, Hibernate sets the database identifier property of the deleted item to null after deletion and flushing, if the option is enabled. It's then a clean transient instance that you can reuse in a future unit of work.

Replicating objects

The operations on the Session we have shown you so far are all common; you need them in every Hibernate application. But Hibernate can help you with some special use cases—for example, when you need to retrieve objects from one database and store them in another. This is called replication of objects.

Replication takes detached objects loaded in one Session and makes them persistent in another Session. These Sessions are usually opened from two different SessionFactorys that have been configured with a mapping for the same persistent class. Here is an example:

Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close();

Session session2 = sessionFactory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(item, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();

The ReplicationMode controls the details of the replication procedure:

  • ReplicationMode.IGNORE—Ignores the object when there is an existing database row with the same identifier in the target database.

  • ReplicationMode.OVERWRITE—Overwrites any existing database row with the same identifier in the target database.

  • ReplicationMode.EXCEPTION—Throws an exception if there is an existing database row with the same identifier in the target database.

  • ReplicationMode.LATEST_VERSION—Overwrites the row in the target database if its version is earlier than the version of the object, or ignores the object otherwise. Requires enabled Hibernate optimistic concurrency control.

You may need replication when you reconcile data entered into different databases, when you're upgrading system configuration information during product upgrades (which often involves a migration to a new database instance), or when you need to roll back changes made during non-ACID transactions.

You now know the persistence lifecycle and the basic operations of the persistence manager. Using these together with the persistent class mappings we discussed in earlier chapters, you may now create your own small Hibernate application. Map some simple entity classes and components, and then store and load objects in a stand-alone application. You don't need a web container or application server: Write a main() method, and call the Session as we discussed in the previous section.

In the next sections, we cover the detached object state and the methods to reattach and merge detached objects between persistence contexts. This is the foundation knowledge you need to implement long units of work—conversations. We assume that you're familiar with the scope of object identity as explained earlier in this chapter.

9.3.2. Working with detached objects

Modifying the item after the Session is closed has no effect on its persistent representation in the database. As soon as the persistence context is closed, item becomes a detached instance.

If you want to save modifications you made to a detached object, you have to either reattach or merge it.

Reattaching a modified detached instance

A detached instance may be reattached to a new Session (and managed by this new persistence context) by calling update() on the detached object. In our experience, it may be easier for you to understand the following code if you rename the update() method in your mind to reattach()—however, there is a good reason it's called updating.

The update() method forces an update to the persistent state of the object in the database, always scheduling an SQL UPDATE. See listing 9.6 for an example of detached object handling.

Listing 9-6. Updating a detached instance
item.setDescription(...); // Loaded in previous Session;

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

sessionTwo.update(item);

item.setEndDate(...);

tx.commit();
sessionTwo.close();

It doesn't matter if the item object is modified before or after it's passed to update(). The important thing here is that the call to update() is reattaching the detached instance to the new Session (and persistence context). Hibernate always treats the object as dirty and schedules an SQL UPDATE., which will be executed during flush. You can see the same unit of work in figure 9.8.

You may be surprised and probably hoped that Hibernate could know that you modified the detached item's description (or that Hibernate should know you did not modify anything). However, the new Session and its fresh persistence context don't have this information. Neither does the detached object contain some internal list of all the modifications you've made. Hibernate has to assume that an UDPATE in the database is needed. One way to avoid this UDPATE statement is to configure the class mapping of Item with the select-before-update="true" attribute. Hibernate then determines whether the object is dirty by executing a SELECT statement and comparing the object's current state to the current database state.

Figure 9-8. Reattaching a detached object

If you're sure you haven't modified the detached instance, you may prefer another method of reattachment that doesn't always schedule an update of the database.

Reattaching an unmodified detached instance

A call to lock() associates the object with the Session and its persistence context without forcing an update, as shown in listing 9.7.

Listing 9-7. Reattaching a detached instance with lock()
Session sessionTwo = sessionFactory.openSession();
Transaction tx = sessionTwo.beginTransaction();

sessionTwo.lock(item, LockMode.NONE);

item.setDescription(...);
item.setEndDate(...);

tx.commit();
sessionTwo.close();

In this case, it does matter whether changes are made before or after the object has been reattached. Changes made before the call to lock() aren't propagated to the database, you use it only if you're sure the detached instance hasn't been modified. This method only guarantees that the object's state changes from detached to persistent and that Hibernate will manage the persistent object again. Of course, any modifications you make to the object once it's in managed persistent state require updating of the database.

We discuss Hibernate lock modes in the next chapter. By specifying LockMode.NONE here, you tell Hibernate not to perform a version check or obtain any database-level locks when reassociating the object with the Session. If you specified LockMode.READ, or LockMode.UPGRADE, Hibernate would execute a SELECT statement in order to perform a version check (and to lock the row(s) in the database for updating).

Making a detached object transient

Finally, you can make a detached instance transient, deleting its persistent state from the database, as in listing 9.8.

Listing 9-8. Making a detached object transient using delete()
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.delete(item);

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

This means you don't have to reattach (with update() or lock()) a detached instance to delete it from the database. In this case, the call to delete() does two things: It reattaches the object to the Session and then schedules the object for deletion, executed on tx.commit(). The state of the object after the delete() call is removed.

Reattachment of detached objects is only one possible way to transport data between several Sessions. You can use another option to synchronize modifications to a detached instance with the database, through merging of its state.

Merging the state of a detached object

Merging of a detached object is an alternative approach. It can be complementary to or can replace reattachment. Merging was first introduced in Hibernate to deal with a particular case where reattachment was no longer sufficient (the old name for the merge() method in Hibernate 2.x was saveOrUpdateCopy()). Look at the following code, which tries to reattach a detached object:

item.getId(); // The database identity is "1234"
item.setDescription(...);

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

Item item2 = (Item) session.get(Item.class, new Long(1234));

session.update(item); // Throws exception!

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

Given is a detached item object with the database identity 1234. After modifying it, you try to reattach it to a new Session. However, before reattachment, another instance that represents the same database row has already been loaded into the persistence context of that Session. Obviously, the reattachment through update() clashes with this already persistent instance, and a NonUniqueObjectException is thrown. The error message of the exception is A persistent instance with the same database identifier is already associated with the Session! Hibernate can't decide which object represents the current state.

You can resolve this situation by reattaching the item first; then, because the object is in persistent state, the retrieval of item2 is unnecessary. This is straightforward in a simple piece of code such as the example, but it may be impossible to refactor in a more sophisticated application. After all, a client sent the detached object to the persistence layer to have it managed, and the client may not (and shouldn't) be aware of the managed instances already in the persistence context.

You can let Hibernate merge item and item2 automatically:

item.getId() // The database identity is "1234"
item.setDescription(...);

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

Item item2 = (Item) session.get(Item.class, new Long(1234)); 
Item item3 = (Item) session.merge(item);
(item == item2) // False (item == item3) // False (item2 == item3) // True return item3; tx.commit(); session.close();

Look at this unit of work in figure 9.9.

Figure 9-9. Merging a detached instance into a persistent instance

The merge(item) call ❸ results in several actions. First, Hibernate checks whether a persistent instance in the persistence context has the same database identifier as the detached instance you're merging. In this case, this is true: item and item2, which were loaded with get() ❷, have the same primary key value.

If there is an equal persistent instance in the persistence context, Hibernate copies the state of the detached instance onto the persistent instance ❹. In other words, the new description that has been set on the detached item is also set on the persistent item2.

If there is no equal persistent instance in the persistence context, Hibernate loads it from the database (effectively executing the same retrieval by identifier as you did with get()) and then merges the detached state with the retrieved object's state. This is shown in figure 9.10.

Figure 9-10. Merging a detached instance into an implicitly loaded persistent instance

If there is no equal persistent instance in the persistence context, and a lookup in the database yields no result, a new persistent instance is created, and the state of the merged instance is copied onto the new instance. This new object is then scheduled for insertion into the database and returned by the merge() operation.

An insertion also occurs if the instance you passed into merge() was a transient instance, not a detached object.

The following questions are likely on your mind:

  • What exactly is copied from item to item2? Merging includes all value-typed properties and all additions and removals of elements to any collection.

  • What state is item in? Any detached object you merge with a persistent instance stays detached. It doesn't change state; it's unaffected by the merge operation. Therefore, item and the other two references aren't the same in Hibernate's identity scope. (The first two identity checks in the last example.) However, item2 and item3 are identical references to the same persistent in-memory instance.

  • Why is item3 returned from the merge() operation? The merge() operation always returns a handle to the persistent instance it has merged the state into. This is convenient for the client that called merge(), because it can now either continue working with the detached item object and merge it again when needed, or discard this reference and continue working with item3. The difference is significant: If, before the Session completes, subsequent modifications are made to item2 or item3 after merging, the client is completely unaware of these modifications. The client has a handle only to the detached item object, which is now getting stale. However, if the client decides to throw away item after merging and continue with the returned item3, it has a new handle on up-to-date state. Both item and item2 should be considered obsolete after merging.

Merging of state is slightly more complex than reattachment. We consider it an essential operation you'll likely have to use at some point if you design your application logic around detached objects. You can use this strategy as an alternative for reattachment and merge every time instead of reattaching. You can also use it to make any transient instance persistent. As you'll see later in this chapter, this is the standardized model of Java Persistence; reattachment isn't supported.

We haven't paid much attention so far to the persistence context and how it manages persistent objects.

9.3.3. Managing the persistence context

The persistence context does many things for you: automatic dirty checking, guaranteed scope of object identity, and so on. It's equally important that you know some of the details of its management, and that you sometimes influence what goes on behind the scenes.

Controlling the persistence context cache

The persistence context is a cache of persistent objects. Every object in persistent state is known to the persistence context, and a duplicate, a snapshot of each persistent instance, is held in the cache. This snapshot is used internally for dirty checking, to detect any modifications you made to your persistent objects.

Many Hibernate users who ignore this simple fact run into an OutOfMemoryException. This is typically the case when you load thousands of objects in a Session but never intend to modify them. Hibernate still has to create a snapshot of each object in the persistence context cache and keep a reference to the managed object, which can lead to memory exhaustion. (Obviously, you should execute a bulk data operation if you modify thousands of objects—we'll get back to this kind of unit of work in chapter 12, section 12.2, "Bulk and batch operations.")

The persistence context cache never shrinks automatically. To reduce or regain the memory consumed by the persistence context in a particular unit of work, you have to do the following:

  • Keep the size of your persistence context to the necessary minimum. Often, many persistent instances in your Session are there by accident—for example, because you needed only a few but queried for many. Make objects persistent only if you absolutely need them in this state; extremely large graphs can have a serious performance impact and require significant memory for state snapshots. Check that your queries return only objects you need. As you'll see later in the book, you can also execute a query in Hibernate that returns objects in read-only state, without creating a persistence context snapshot.

  • You can call session.evict(object) to detach a persistent instance manually from the persistence context cache. You can call session.clear() to detach all persistent instances from the persistence context. Detached objects aren't checked for dirty state; they aren't managed.

  • With session.setReadOnly(object, true), you can disable dirty checking for a particular instance. The persistence context will no longer maintain the snapshot if it's read-only. With session.setReadOnly(object, false), you can re-enable dirty checking for an instance and force the recreation of a snapshot. Note that these operations don't change the object's state.

At the end of a unit of work, all the modifications you made have to be synchronized with the database through SQL DML statements. This process is called flushing of the persistence context.

Flushing the persistence context

The Hibernate Session implements write-behind. Changes to persistent objects made in the scope of a persistence context aren't immediately propagated to the database. This allows Hibernate to coalesce many changes into a minimal number of database requests, helping minimize the impact of network latency. Another excellent side-effect of executing DML as late as possible, toward the end of the transaction, is shorter lock durations inside the database.

For example, if a single property of an object is changed twice in the same persistence context, Hibernate needs to execute only one SQL UPDATE. Another example of the usefulness of write-behind is that Hibernate is able to take advantage of the JDBC batch API when executing multiple UPDATE, INSERT, or DELETE statements.

The synchronization of a persistence context with the database is called flushing. Hibernate flushes occur at the following times:

  • When a Transaction on the Hibernate API is committed

  • Before a query is executed

  • When the application calls session.flush() explicitly

Flushing the Session state to the database at the end of a unit of work is required in order to make the changes durable and is the common case. Note that automatic flushing when a transaction is committed is a feature of the Hibernate API! Committing a transaction with the JDBC API doesn't trigger a flush. Hibernate doesn't flush before every query. If changes are held in memory that would affect the results of the query, Hibernate synchronizes first by default.

You can control this behavior by explicitly setting the Hibernate FlushMode via a call to session.setFlushMode(). The default flush mode is FlushMode.AUTO and enables the behavior described previously. If you chose FlushMode.COMMIT, the persistence context isn't flushed before query execution (it's flushed only when you call Transaction.commit() or Session.flush() manually). This setting may expose you to stale data: Modifications you make to managed objects only in memory may conflict with the results of the query. By selecting FlushMode.MANUAL, you may specify that only explicit calls to flush() result in synchronization of managed state with the database.

Controlling the FlushMode of a persistence context will be necessary later in the book, when we extend the context to span a conversation.

Repeated flushing of the persistence context is often a source for performance issues, because all dirty objects in the persistence context have to be detected at flush-time. A common cause is a particular unit-of-work pattern that repeats a query-modify-query-modify sequence many times. Every modification leads to a flush and a dirty check of all persistent objects, before each query. A FlushMode.COMMIT may be appropriate in this situation.

Always remember that the performance of the flush process depends in part on the size of the persistence context—the number of persistent objects it manages. Hence, the advice we gave for managing the persistence context, in the previous section, also applies here.

You've now seen the most important strategies and some optional ones for interacting with objects in a Hibernate application and what methods and operations are available on a Hibernate Session. If you plan to work only with Hibernate APIs, you can skip the next section and go directly to the next chapter and read about transactions. If you want to work on your objects with Java Persistence and/or EJB 3.0 components, read on.

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

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