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 transactionnamely, 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 you have ever used a version control system, you've seen one of these two strategies in action. For example, Microsoft Visual SourceSafe uses a pessimistic system in which locks are explicitly managed by the developer (but conflicts are impossible), and CVS uses an optimistic system with no locks (but occasionally requiring merges to deal with conflicts). |
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.
Listing 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 changesboth 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 versioningthe likelihood of contention is quite low if a resource is owned by only a single user.
 |