Wednesday Jul 02, 2008
Wednesday Jul 02, 2008
by Rahul Biswas
In this tip, you'll learn how to prevent non-repeatable reads through the Java Persistence API (JPA) and JPA extensions provided by EclipseLink.
Non-Repeatable Reads
A non-repeatable read is a concept pertinent to database transactions. In a non-repeatable read, multiple reads of a data item from a datasource do not return the same value for the data item. In JPA terms, a non-repeatable read means that within a transaction, if an application reads the same entity multiple times from a datasource, the application will find that the entity state has changed between reads.
Non-repeatable reads apply to the following scenario: A transaction, T1, reads a row in a database. Another transaction, T2, then modifies or deletes that row before T1 has committed. Both transactions eventually commit successfully.
Generally one of the following approaches is used to prevent that scenario:
Consider, for example, the following simple table in a database:
Suppose an application, A, starts a transaction, T1, and queries the table to retrieve the price for "Expensive Item". Suppose A then starts another transaction, T2, to update the price for "Expensive Item".
Figure 1 illustrates the optimistic approach. Transaction T1 assumes that data is not being concurrently modified, although, in fact, transaction T2 does concurrently modify the data and proceeds with the commit. When T1 attempts to commit, it detects the change and notifies the application, which may rollback the T1 transaction.
|
|
Figure 1. Preventing Non-Repeatable Reads Using an Optimistic Approach |
Figure 2 illustrates the pessimistic approach. Here, the T2 update is blocked until the T1 transaction commits.
|
|
Figure 2. Preventing Non-Repeatable Reads Using a Pessimistic Approach |
Preventing Non-Repeatable Reads in JPA
You can use JPA to prevent non-repeatable reads on versioned entities.
A versioned entity is marked with the @Version annotation, as illustrated
in the following code snippet:
@Entity
public class StockQuote implements Serializable {
@Version
public Long getVersion() {
return version;
}
and its corresponding database schema has a version column, such as that created by the following SQL statement:
CREATE TABLE STOCKQUOTE
(ID NUMBER NOT NULL, VERSION NUMBER, PRICE FLOAT, DESCRIPTION VARCHAR(255),
PRIMARY KEY (ID));
Versioning enables JPA to manage optimistic locking. Optimistic locking assumes that there will be infrequent conflicts between concurrent transactions. In optimistic locking, the objective is to give concurrent transactions a lot of freedom to process simultaneously, but to detect and prevent collisions.
The way you prevent non-repeatable reads in JPA on a versioned entity is through the lock() method of
the EntityManager class. Here is the method signature:
public void lock(Object entity, LockModeType lockMode);
The first method parameter is the entity instance that needs to be locked in the transaction.
The second method parameter is the lock mode, which can have one of the following values:
Both lock modes prevent non-repeatable reads. However, the WRITE lock mode also forces the version column to be updated.
As illustrated in Figure 1, transaction T1 acquires a read lock, but
transaction T2 is allowed to commit its changes. When transaction T1 tries to commit, the EclipseLink JPA implementation
detects that the data changed since the last read. It does this by checking the version column.
It then throws an OptimisticLockException to
application A. At this point, the application can retry the operation after refreshing
the value of the entity. Alternatively, the application can abort the operation and rollback the transaction.
Taking Advantage of EclipseLink Extensions
EclipseLink is an open source project whose goal is to provide a comprehensive persistence framework that will run in any Java environment and that will support the reading and writing of objects to and from virtually any type of data source. One of the project's deliverables is an advanced features extension to JPA. You can take advantage of this extension to ensure repeatable reads through pessimitic locking, something that is not currently supported in the JPA 1.0 specification. However, this solution is not portable because it uses EclipseLink-specific extensions.
The way to ensure repeatable reads with EclipseLink is through its support for pessimistic locking on JPA Query Language (JPA QL) queries. Pessimistic locking assumes that there will be frequent conflicts between concurrent transactions. To prevent collisions during pessimistic locking, an entity is locked in the database for the entire time that it is in application memory.EclipseLink enables pessimistic locks on JPA QL queries through query hints, which are JPA extension points for vendor-specific query features.
There are two ways to enable a pessimistic lock through a JPA QL query hint. One way is to use
a @NamedQuery annotation, as in the following
example:
@NamedQuery(
name="GetStock"
query="select sq from StockQuote as sq where sq.id = :id"
hints={@QueryHint(name=EclipseLinkQueryHints.PESSIMISTIC_LOCK,
value=PessimisticLock.Lock)})
The other way is to use the Query API, as in the following example:
Query q = em.createNamedQuery("GetStock");
q.setHint(EclipseLinkQueryHints.PESSIMISTIC_LOCK,
PessimisticLock.Lock);
q.setParameter("id", 1);
Acquiring a pessimistic lock through either of these techniques typically locks the pertinent row in the underlying database. As illustrated in Figure 2, if transaction T1 runs either of the two queries above, it locks the underlying datasource row and blocks T2 from committing its updates. After T1 commits, T2 can commit its changes.
Sample Application
Let's look at a sample application that prevents non-repeatable reads through EclipseLink JPA extensions. In fact, the application also allows non-repeatable reads so that you can compare the results of both types of reads. You can find the application in the sample package that accompanies this tip.
The sample application is a simplified version of a stock market application that would typically handle a large number of concurrent transactions, and in doing so, service simultaneous reads and writes to entities. In general, here's what the application does:
createDDL.ddbc):
persistence.xml file in the
META-INF directory of an application. Here is the content of the persistence.xml file for the sample
application:
ReaderThread and a WriterThread thread. The application can run
in one of three modes, repeatable read with optimistic locking, repeatable read with pessimistic locking or
non-repeatable read. You indicate which mode by specifying rr and o, for repeatable read with
optimistic locking, rr and p, for repeatable read with pessimistic locking, or nrr,
for non-repeatable read, when you start the application. The application uses the
entries (or entry) as arguments when it starts
the ReaderThread. The code for this part of the application is in file Main.java. Here is
a snippet of that code:
In repeatable read with optimistic locking mode, the ReaderThread begins a transaction
on an entity. The WriterThread then updates the stock price during the ReaderThread
transaction and successfully commits. The ReaderThread attempts to commit its transaction.
However, the underlying JPA implementation detects that the corresponding database row has been updated by
another thread after it was last read, so it throws an OptimisticLockException. The application
catches this exception and flags the transaction as failed.
In repeatable read with pessimistic locking mode, the ReaderThread begins a transaction
on an entity. The WriterThread then attempts to update the stock price during the
ReaderThread transaction. However, the update attempt is blocked because the ReaderThread
has a lock on both the entity object and the underlying database row representing the object.
In non-repeatable read mode, the ReaderThread begins a transaction on an entity. Then the
WriterThread updates the stock price on the same entity and commits its transaction before the
ReaderThread commits its transaction. The ReaderThread detects the
conflict by detecting the difference in the price and prints out a warning message.
Here is part of the code in the ReaderThread:
ReaderThread enables repeatable reads through pessimistic locking in a JPA QL query hint:
Running the Sample Application
A sample package accompanies this tip. To install and run the application in the sample package:
<sample_install_dir>/jpa-repeatable-read.tech-tip, where <sample_install_dir>
is the directory where you installed the sample package. For example, if you extracted the contents to C:\
on a Windows machine, then your newly created directory should be at C:\jpa-repeatable-read.tech-tip.jpa-repeatable-read.tech-tip directory and set the values for following properties in the
build.xml file as appropriate for your operating environment: jdbc.url, db.userid,
db.password, and javaee.home. The application uses these property settings
to update the persistence.xml file and in the initial setup on the database.jpa-repeatable-read.tech-tip directory:
ant setup
build.xml file have the values rr and
o as follows:jpa-repeatable-read.tech-tip directory:
ant run allYou should see output similar to the following:
WriterThread and throws an OptimisticLockException.
Then the ReaderThread catches the exception and flags the transaction as failed.
build.xml file have the values rr and p
as follows:
<target name="run" depends="clean, pusetup, compile">
<java classname="com.sun.techtip.sample.Main" fork="true">
<arg value="rr"/>
<arg value="p"/>
jpa-repeatable-read.tech-tip directory:
ant run allYou should see output similar to the following:
WriterThread is blocked from committing the transaction until
the ReaderThread transaction has committed.
build.xml file to the value
nrr. Then enter the following command in the jpa-repeatable-read.tech-tip directory:
ant run allYou should see output similar to the following:
ReaderThread retrieves a value of 49.30 the first time it requests
the price. The WriterThread is allowed to update the value to 54.23 concurrent with the ReaderThread
transaction. The second ReaderThread read retrieves the updated price because it is made after the
WriterThread commits its transaction.
Note: Although the application should run with various types of databases, it has been tested only with an Oracle database..
Further Reading
About the Author
Rahul Biswas is a member of the Java Performance Engineering group at Sun.
This is the 1000th approach to locking what...ah, databases that already support locking. Get rid of database specific functions. Why not extent JPA with a LOCK column like it's provided VERSION column, so you can have both, optimistic and pessimistic locking on row level. (I recently ran accross such a sollution, easy to handle even on databases without any sort of locking.)
Posted by Onkobu on July 02, 2008 at 11:49 PM PDT #
Hi Onkobu,
Thanks for your comments. The current JPA 1.0 spec, on which this tech-tip is based, considers pessimistic locking out of the scope of the specification. So, it can only be done in vendor specific manners (which in this case is through EclipseLink specific query hints and relies on the underlying database to do row level locking).
If I understand your LOCK column suggestion, I am not sure how sturdy that approach would be. There are some considerations, such as what will happen if an application that got the lock by updating the lock column crashed?
There is some discussion on having a standard way of doing pessimistic locking in JPA in the 2.0 version of the specification.
You can access the early draft here (JSR 317):
http://www.jcp.org/en/jsr/detail?id=317
And I would also encourage you to post your comments on pessimistic locking to this email list:
jsr-317-comments@jcp.org
Posted by Rahul on July 07, 2008 at 04:03 PM PDT #
I had a look at the JSR and I'm convinced, that handing over control to the persistence manager is sufficient. And I still can't believe, that pessimistic locking requires nothing more than an annotation while optimistic locking forces me to add a version field...what are annotations for? And as a short question-like answer to your "what if" scenario: What if not only the application freezes, but your database host tunnels to another dimension? (Oracle has tried to provide pessimistic locking for decades and there's always a scenario deadlocking even database processes. So flagging a row as read only and propagating this to all dependend records causing a red outline on user's interface is better than showing the hour glass curser...for weeks.) To conclude this: I add a boolean attribute lock to each leaf class. Every depending class uses the and-combined lock-attributes of it's dependencies and that's it. Now the session beans gets something like a select for update method (viewRecord and viewRecordAndLock) and there you go...no magic, no plugins and a simple check box on admin's interface to delete (unwanted) locks. Concurrency is handled by EJB spec.
Posted by Onkobu on July 21, 2008 at 01:02 AM PDT #
Hi Onkobu,
I think the question boils down to who do you want to be responsible for managing the locks, the JPA implementation running within your brand new application or a tried and tested database which is supposed to be 24x7. I am not questioning the possibility, but the reliability of this approach. Having said that, you have some interesting comments and I would again encourage you to bring this up in the jsr-317-comments@jcp.org mailing list. Folks there might have discussed something similar and provide better insights into the reasons behind the choice in JPA 2.0, or they may find your comments enlightening.
Thanks
Rahul
Posted by 192.18.43.225 on July 21, 2008 at 04:16 PM PDT #