Chapter 9. Transactions

This chapter explains transactions and a somewhat related topic, locking. Both of these features require database support, but are critical for ensuring data integrity.

Introduction to Transactions

A transaction is a mechanism for grouping a set of statements together as a unit. A transaction should be atomic, consistent, independent, and durable (ACID). Depending on the application, this capability can be extremely important.

As an example of the importance of a transaction's ACID properties, consider the elements of a bank transaction. Let's say that you wish to move money from a checking account into a savings account, using the conceptual tasks:

  • Verify that checking account has more than $100.

  • Remove $100 from checking account.

  • Add $100 to savings account.

These three statements should be thought of as comprising one transaction, and therefore a failure in any of the three steps means that all of them should be ignored. For example, a failure to add $100 to the savings account means that the $100 should not be removed from the checking account.

This leads to a key aspect of any transaction. A transaction can be started and then either committed if there are no problems or rolled back if there is a problem. A transaction that is rolled back should have no impact on the data in the database.

Hibernate supports transactions, but does not actually provide the transaction implementation. Typically, this functionality is provided by the underlying database. To enable this functionality, the hibernate.transaction.factory_class. property should be set to net.sf.hibernate.transaction.JDBCTransactionFactory. Before relying on JDBC transactions, you should check your database's documentation for more information about transaction support.

Sessions, Transactions, and Flushing

Hibernate uses several terms to describe the logical phases of database interaction. Figure 9.1 illustrates the relationship between a session, a transaction, and a flush.

Database Interaction Phases

Figure 9.1. Database Interaction Phases

A session is a lightweight object, representing a JDBC connection.

A transaction represents an ACID unit of work (with the precise meaning dependent on the underlying database and/or transaction manager).

A flush statement may be used to indicate a specific ordering of the execution of a set of SQL. Certain session methods will cause a flush to occur automatically:

  • Certain calls to Session.find() or Session.iterate()

  • net.sf.hibernate.Transaction.commit()

  • net.sf.hibernate.Session.flush()

Unless a Session.flush() statement is used to override the statement ordering, Hibernate will execute SQL statements in the following order:

  1. Entity insertions

  2. Entity updates

  3. Collection deletions

  4. Collection element deletions, updates, and insertions

  5. Collection insertions

  6. Entity deletions

Within each of these broad operational types, the SQL is executed in the same order in which the method calls are made. For example, consider the following operations:

  • Session.update(object1);

  • Session.delete(object2);

  • Session.update(object3);

When Hibernate translates these operations into SQL, it will first issue SQL to UPDATE the object1 and object3 records, and then DELETE the object2 record.

Under certain circumstances, Hibernate may optimize the generated SQL. In most cases this is the most desirable (and best-performing) behavior, but there are situations (for example, if you have certain database triggers) in which it might be preferable to use a Session.flush() statement to ensure the proper execution of statements.

An exception is that the Session.save() method is called when objects using native generator are inserted.

Optimistic and Pessimistic Locking

Locking refers to the notion that changes to a record made by one person should not overwrite changes made by someone else. Strategies for dealing with this fall into two categories. Pessimistic locking assumes that every record will be subject to contention; optimistic locking assumes that conflicts are the exception, not the rule.

Pessimistic Locks

Pessimistic locking is used to denote a system in which an individual user can check out a record. It is called pessimistic because it assumes the worst-case scenario in every possible transaction—namely, that every transaction will conflict unless a strict monitor is used.

With a pessimistic lock, users may or may not be able to view or edit a record until the lock is released. To support this style of lock, the database must support pessimistic locking. Obtaining and releasing of locks on specific rows of a table can be managed with the net.sf.hibernate.LockMode class in conjunction with certain methods of Session. For example, you can use the method session.load(object, type, LockMode.UPGRADE) for explicit pessimistic locking. While pessimistic locking can be useful in some circumstances the application must take great care to avoid locking objects for an unnecessarily long time. In addition, care must be taken to release every lock at the right time, or else the record will become unavailable for a lengthy period.

Optimistic Locks

Optimistic locking refers to the notion that “checking out” an object and applying changes are two independent steps. For example, consider the post system described in Chapter 2. A single record is used to store a post. A user at work downloads the original version of the post into his or her Swing client and makes some changes, thereby creating version two of the post. The user leaves for home without saving the changes. At home, the user opens the same post, makes a different set of changes, and then saves the post data. This becomes version three. The next morning, the user returns to the Swing client and clicks “save post” on version two of the document.

If the developer had chosen pessimistic locking, the record would have been locked when checked out by the Swing client, and the user would not have been able to save the changes at home (generally speaking, pessimistic locking is easier for the developer, but frustrating for users). Optimistic locking, in contrast, refers to the fact that we “optimistically” assume that no changes are made between when the record is read and when the record is updated. This is only a problem if a “secret” change is made (in the example above, version two).

The user experience for resolving an optimistic conflict will vary depending on the application. In the example given above, when the user clicks “save post” for version three, we probably would like to notify the user that the record has been updated since the last viewing. Ideally, we'd want to let the user compare versions and select the preferred version (as shown in Chapter 2). If the application is an elaborate one, it may even provide a mechanism for merging changes. Listing 9.1 shows an excerpt of the code given in Chapter 2. Note how the net.sf.hibernate.StaleObjectStateException is caught to manage the pessimistic conflict.

Example 9.1. Handling an Optimistic Conflict

try
{
     hibernateSession = AppSession.getSession();
     myTransaction = hibernateSession.beginTransaction();

     myPost = new Post();
     myPost.setId(request.getParameter("postID"));
     myPost.setRevision(
           new Integer(
                 request.getParameter("revision"))
                      .intValue());
     myPost.setTitle(request.getParameter("title"));
     myPost.setDate(new java.util.Date());
     myPost.setSummary(request.getParameter("summary"));
     myPost.setContent(request.getParameter("content"));

     Author myAuthor = new Author();
     myAuthor.setId(request.getParameter("authorID"));
     myPost.setAuthor(myAuthor);

     hibernateSession.update(myPost);

     myTransaction.commit();
     hibernateSession.close();

     redirect_page =
           redirect_page +
                request.getParameter("postID");
     done = true;
}
catch (net.sf.hibernate.StaleObjectStateException stale)
{
     error =
     "This post was updated by another " +
     "transaction. You may either update " +
     "the existing data, or resubmit ";
     "your changes.";
     conflict=true;
}
catch (Exception e) {
     error = e.getMessage(); e.printStackTrace();
     try{ myTransaction.rollback(); }
     catch (Exception e2) {;}
}
finally
{
     try{hibernateSession.close();}
     catch (Exception e) {;}
}

Hibernate supports several different models for detecting this sort of versioning conflict (or optimistic locks). The versioning strategy uses a version column in the table. You can use either a version property tag or a timestamp property tag to indicate a version column for the class (see the appropriate tag for more information). When a record is updated, the versioning column is automatically updated as well. This versioning strategy is generally considered the best way to check for changes—both for performance and for compatibility with database access that occurs outside of Hibernate. For example, you can simply issue a SELECT to obtain a record by id, and include the version column in the WHERE clause to indicate the specific record you wish to UDPATE; a failure to update is an easy way to detect that the record is not properly synchronized. This is precisely the functionality as provided by the StaleObjectException. An example of the versioning strategy (and a user interface for managing conflicts) is shown in Chapter 2.

The dirty strategy only compares the columns that need to be updated. For example, let's say you load a Cat object, with a name (“Tom”), weight, and color. You change the name to “Bob” and want to save the change. Hibernate will verify that the current Cat name is “Tom” before updating the name to “Bob.” This is computationally intensive compared to the versioning strategy, but can be useful in situations in which a version column is not feasible.

The all strategy compares all the columns, verifying that the entire Cat is as it was when loaded before saving. Using the Cat example in the preceding paragraph, Hibernate will verify that the name, weight, and color are as loaded before persisting the change to the name. While more secure than the dirty strategy, this strategy, too, is more computationally intensive.

Finally, the none strategy can be used to ignore optimistic locking. Updated columns are updated, period. This obviously performs better than any other strategy but is likely to lead to problems if you have more than a single user updating related records.

Generally speaking, I recommend using the versioning strategy to manage conflicts if at all possible. The pessimistic model, unless very carefully managed, can lead to more problems than it solves, and the dirty and all mechanisms are not very high-performance. Also, don't go overboard with versioning—the likelihood of contention is quite low if a resource is owned by only a single user.

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

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