X

Preventing Non-Repeatable Reads in JPA Using EclipseLink

Guest Author

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:

  • Optimistic. This approach assumes that the same data is not being read and written
    concurrently. The write transaction is allowed to commit. However, the read transaction
    is required to detect the changed value when it attempts to commit.
  • Pessimistic. This approach assumes that the same data may be read and written
    concurrently. The read transaction locks the row representing the data in the underlying datasource.
    The write transaction can commit only after the read commits. During the
    read transaction the value of the data being locked does not change.

Consider, for example, the following simple table in a database:
IdDescriptionPrice720Expensive Item582.99721Nice to have Item66.99

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.














Preventing Non-Repeatable Reads Using an Optimistic Approachalign="bottom" border="0" width="349" height="336">

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.













Preventing Non-Repeatable Reads Using a Pessimistic Approachalign="bottom" border="0" width="373" height="336">

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:

  • READ
  • WRITE

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:


  1. Creates a table in a database. Here are the SQL statements that create the table (you can find this code in
    file createDDL.ddbc):

       CREATE TABLE STOCKQUOTE
    (ID NUMBER NOT NULL,
    VERSION INTEGER,
    PRICE FLOAT,
    DESCRIPTION VARCHAR(255),
    PRIMARY KEY (ID))
    ;
    INSERT INTO STOCKQUOTE (ID, PRICE, VERSION, DESCRIPTION)
    VALUES(1, 23, 1, 'JAVA')
    ;
    CREATE TABLE SEQUENCE (
    SEQ_NAME VARCHAR(50) NOT NULL,
    SEQ_COUNT INTEGER,
    PRIMARY KEY (SEQ_NAME))
    ;
    INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 1);




     


    Notice the VERSION column in the table.
  2. Establishes a persistence unit that specifies the metadata files, classes, and JAR files for the persisted
    entities. The persistence unit is always specified in the persistence.xml file in the
    META-INF directory of an application. Here is the content of the persistence.xml file for the sample
    application:

       <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0"
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="tech-tip-4-samplePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>com.sun.techtip.sample.jpa.entity.StockQuote</class>
    <properties>
    <property name="eclipselink.jdbc.user" value="[your-db-userid]"/>
    <property name="eclipselink.jdbc.password" value="[your-db-password]"/>
    <property name="eclipselink.jdbc.url" value="jdbc:oracle:thin:@[your-host]:[your-port]:[your-db-sid]"/>
    <property name="eclipselink.jdbc.driver" value="oracle.jdbc.driver.OracleDriver"/>
    </properties>
    </persistence-unit>
    </persistence>




     


    Notice that the persistence unit specifies EclipseLink as the JPA persistence provider.

  3. Creates two worker threads, a 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:

       public class Main {
    private static final String NRR = "nrr";
    private static final String RR="rr";
    private static final String P="p";
    private static final String O="o";
    public Main() {
    }
    public static void main(String[] args){
    if(args==null || args.length < 1){
    displayUsage();
    System.exit(-1);
    }
    boolean bLock = RR.equals(args[0].trim());
    if(args.length < 2 && bLock){
    displayUsage();
    System.exit(-1);
    }
    boolean isPessimistic = bLock? P.equals(args[1].trim()): false;
    System.out.println("Running sample for " +
    (bLock? "Repeatable read ":"Non-repeatable read ") +
    (bLock? (isPessimistic? "in pessimistic mode":"in optimisitic mode"):
    ""));
    //create two worker threads with lock options
    ReaderThread rt = new ReaderThread(bLock, isPessimistic);
    WriterThread wt = new WriterThread();
    //start the run
    new Thread(rt).start();
    new Thread(wt).start();




     


  4. 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:



       public class ReaderThread implements Runnable{
    private EntityManagerFactory emf;
    private boolean bLock;
    private boolean isPessimistic;
    public ReaderThread(boolean bLock, boolean isPessimistic) {
    this.bLock = bLock;
    this.isPessimistic=isPessimistic;
    }
    public void run() {
    emf = DataSourceManager.getInstance().getEMF();
    read();
    }
    private void read(){
    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();
    StockQuote sq;
    if(bLock){
    if (isPessimistic) {
    Query q = em.createNamedQuery("GetStock");
    q.setParameter("id", 1);
    q.setHint(EclipseLinkQueryHints.PESSIMISTIC_LOCK,
    PessimisticLock.Lock);
    sq = (StockQuote) q.getSingleResult();
    }else{
    sq = em.find(StockQuote.class, new Long(1));
    em.lock(sq, LockModeType.READ);
    }
    }else{
    sq = em.find(StockQuote.class, new Long(1));
    }
    double price = sq.getPrice();
    System.out.println("ReaderThread: original price - "+ price);
    //now wait for write thread to do it's update
    while(!WriterThread.readyToWrite.get()){
    try{
    System.out.println("ReaderThread: waiting for writer thread to" +
    " be ready...");
    Thread.currentThread().sleep(100);
    }catch(InterruptedException iex){
    iex.printStackTrace();
    }
    }
    WriterThread.attemptWrite.set(true);
    System.out.println("ReaderThread: giving WriterThread OK to commit...");
    //write thread has attempted it's write
    System.out.println("ReaderThread: hit enter to continue....");
    try{
    System.in.read();
    }catch(Exception ex){
    ex.printStackTrace();
    }
    if(!bLock || (bLock && isPessimistic)){
    em.refresh(sq);
    }
    double _price = sq.getPrice();
    try{
    em.getTransaction().commit();
    System.out.println("ReaderThread: latest price - " + _price);
    if (_price != price && !bLock) {
    System.out.println("Price was modified by the writer thread, " +
    "repeatable read failed as expected.");
    } else if (!bLock) {
    System.out.println("Price did not change inspite of " +
    "concurrent update!");
    } else if (_price == price && bLock) {
    System.out.println("Repeatable read succeeded!");
    } else {
    System.out.println("The app failed in testing " +
    "repeatable reads, please check your DB supports " +
    "row locking.");
    }
    }catch(Exception ex){
    System.out.println("ReaderThread: Transaction failed with following" +
    " message -- "+ex.getMessage());
    }
    }
    }




     


    Notice that the ReaderThread enables repeatable reads through pessimistic locking in a JPA QL query hint:

       if(bLock){
    if (isPessimistic) {
    Query q = em.createNamedQuery("GetStock");
    q.setParameter("id", 1);
    q.setHint(EclipseLinkQueryHints.PESSIMISTIC_LOCK,
    PessimisticLock.Lock);
    sq = (StockQuote) q.getSingleResult();
    }else{
    sq = em.find(StockQuote.class, new Long(1));
    em.lock(sq, LockModeType.READ);
    }
    }





Running the Sample Application

A
sample package accompanies this tip. To install and run the application in the
sample package:


  1. Download the sample package and extract its contents. You should now see a newly extracted directory
    <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.
  2. Change to the 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.
  3. Set up the database and establish the persistence unit by entering the following command in the
    jpa-repeatable-read.tech-tip directory:
       ant setup

  4. Run the application in repeatable read with optimistic lock mode. Ensure that the arguments
    of the run target in the build.xml file have the values rr and
    o as follows:


  5.    <target name="run" depends="clean, pusetup, compile">
    <java classname="com.sun.techtip.sample.Main" fork="true">
    <arg value="rr"/>
    <arg value="o"/>





    Then enter the following command in the jpa-repeatable-read.tech-tip directory:
       ant run all

    You should see output similar to the following:

       Running sample for Repeatable read in optimisitic mode
    [EL Info]: 2008.07.01 12:46:16.500--ServerSession(17423963)--EclipseLink, version: Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080604)
    [EL Info]: 2008.07.01 12:46:19.343--ServerSession(17423963)--file:/D:/work/articles/jpa-repeatable-read.tech-tip/build/classes/-tech-tip-4-samplePU login successful
    WriterThread: original price - 40.745903
    WriterThread: ready, need OK from ReaderThread...
    ReaderThread: original price - 40.745903
    ReaderThread: giving WriterThread OK to commit...
    ReaderThread: hit enter to continue....
    WriterThread: starting to commit now...
    WriterThread: committed tx...
    WriterThread: updated price - 44.8204933
    [EL Warning]: 2008.07.01 12:47:37.906--UnitOfWork(24118161)--Exception [EclipseLink-5006] (Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080604)): org.eclipse.persistence.exceptions.OptimisticLockException
    Exception Description: The object [com.sun.techtip.sample.jpa.entity.StockQuote@878c4c] cannot be updated because it has changed or been deleted since it was last read.
    Class> com.sun.techtip.sample.jpa.entity.StockQuote Primary Key> [1]
    [EL Warning]: 2008.07.01 12:47:37.921--UnitOfWork(24118161)--javax.persistence.OptimisticLockException: Exception [EclipseLink-5006] (Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080604)): org.eclipse.persistence.exceptions.OptimisticLockException
    Exception Description: The object [com.sun.techtip.sample.jpa.entity.StockQuote@878c4c] cannot be updated because it has changed or been deleted since it was last read.
    Class> com.sun.techtip.sample.jpa.entity.StockQuote Primary Key> [1]
    ReaderThread: Transaction failed with following message -- javax.persistence.OptimisticLockException: Exception [EclipseLink-5006] (Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080604)): org.eclipse.persistence.exceptions.OptimisticLockException
    Exception Description: The object [com.sun.techtip.sample.jpa.entity.StockQuote@878c4c] cannot be updated because it has changed or been deleted since it was last read.
    Class> com.sun.techtip.sample.jpa.entity.StockQuote Primary Key> [1]




     


    Notice that the non-repeatable read was prevented. The underlying JPA implementation detects a change to the price by the
    WriterThread and throws an OptimisticLockException.
    Then the ReaderThread catches the exception and flags the transaction as failed.


  6. Run the application in repeatable read with pessimistic lock mode. Ensure that the
    arguments of the run target in the build.xml file have the values rr and p
    as follows:
  7.    <target name="run" depends="clean, pusetup, compile">
    <java classname="com.sun.techtip.sample.Main" fork="true">
    <arg value="rr"/>
    <arg value="p"/>




Then enter the following command in the jpa-repeatable-read.tech-tip directory:
   ant run all

You should see output similar to the following:

   Running sample for Repeatable read in pessimistic mode
[EL Info]: 2008.07.01 12:53:50.078--ServerSession(17423963)--EclipseLink, version: Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080604)
[EL Info]: 2008.07.01 12:53:51.875--ServerSession(17423963)--file:/D:/work/articles/jpa-repeatable-read.tech-tip/build/classes/-tech-tip-4-samplePU login successful
WriterThread: original price - 44.8204933
WriterThread: ready, need OK from ReaderThread...
ReaderThread: original price - 44.8204933
ReaderThread: giving WriterThread OK to commit...
ReaderThread: hit enter to continue....
WriterThread: starting to commit now...
ReaderThread: latest price - 44.8204933
Repeatable read succeeded!
WriterThread: committed tx...
WriterThread: updated price - 49.302542630000005




 


Notice that the repeatable read succeeded. The WriterThread is blocked from committing the transaction until
the ReaderThread transaction has committed.


  • Run the application in non-repeatable read mode. Change the run target in the build.xml file to the value
    nrr. Then enter the following command in the jpa-repeatable-read.tech-tip directory:
       ant run all

    You should see output similar to the following:

       Running sample for Non-repeatable read
    [EL Info]: 2008.07.01 12:55:33.906--ServerSession(17423963)--EclipseLink, version: Eclipse Persistence Services - 1.0 (Build SNAPSHOT - 20080604)
    [EL Info]: 2008.07.01 12:55:35.734--ServerSession(17423963)--file:/D:/work/articles/jpa-repeatable-read.tech-tip/build/classes/-tech-tip-4-samplePU login successful
    ReaderThread: original price - 49.30254263
    ReaderThread: waiting for writer thread to be ready...
    WriterThread: original price - 49.30254263
    WriterThread: ready, need OK from ReaderThread...
    ReaderThread: giving WriterThread OK to commit...
    ReaderThread: hit enter to continue....
    WriterThread: starting to commit now...
    WriterThread: committed tx...
    WriterThread: updated price - 54.232796893
    ReaderThread: latest price - 54.232796893
    Price was modified by the writer thread, repeatable read failed as expected.




     


    Notice that the repeatable read failed. The 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.

  • Join the discussion

    Comments ( 6 )
    • Onkobu Wednesday, July 2, 2008

      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.)


    • Rahul Monday, July 7, 2008

      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


    • Onkobu Monday, July 21, 2008

      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.


    • guest Monday, July 21, 2008

      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


    • Sebastien Tardif Saturday, March 21, 2009

      Repeatable read is already provided by default by EclipseLink and Hibernate, because after an object is read it will not be refreshed from database for the duration of the transaction. However, same query can find new object during the life of the transaction, so phantom read.

      It's a useful article but I think it's not using correctly the term repeatable read.

      Title could have been: How to implement optimistic locking in JPA EclipseLink.

      http://en.wikipedia.org/wiki/Isolation_(database_systems)


    • Rahul Sunday, March 22, 2009

      Hi Sebastien,

      Thanks for your comment. The tech-tip explains how to prevent non-repeatable reads and the example considers a scenario where the data has changed in the database since it was read.

      While in JPA implementations you will by default have repeatable reads because an EntityManager find does not go to the database, you could be working with stale data because it may have changed on the database by another transaction. Unless you do an EntityManager lock (which is demonstrated in the example) or the transaction writes the same entity to the database, your application may not realize that it was working with stale data.

      Optimistic or Pessimistic Locking are the mechanisms for ensuring that we can prevent the non-repeatable reads.

      Rahul


    Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.