liveBook · Manning

Race condition

As explained in Java Persistence with Spring Data and Hibernate epub

A race condition is a correctness problem in which the outcome depends on the timing or interleaving of concurrent operations.

A race condition can arise when accepting an auction bid. The application reads the highest bid, checks that a new bid is higher, then stores it. Another transaction may place a bid between the read and the insert, leaving the comparison based on stale data. section 11.2.2

Versioning the Item alone does not detect this conflict: inserting a separate Bid does not modify the Item. Use JPA’s OPTIMISTIC_FORCE_INCREMENT lock mode on the Item when reading it. The forced version change makes a concurrent conflict detectable at commit. If bids instead belong to an Item @ElementCollection, adding one increments the Item version without a forced increment. section 11.2.2

Listing 11.5 Forcing a version increment of an entity instance
Path: /Ch11/transactions/src/test/java/com/manning/javapersistence/ch11
 /concurrency/Versioning.java – forceIncrement()
 
   EntityManager em = emf.createEntityManager();
   em.getTransaction().begin();
Item item = em.find(                                          #1
    Item.class,
    ITEM_ID,
    LockModeType.OPTIMISTIC_FORCE_INCREMENT
);
Bid highestBid = queryHighestBid(em, item);
// . . .  Another transaction changes the record
Bid newBid = new Bid(
        new BigDecimal("45.45"),
        item,
        highestBid);
em.persist(newBid);                                           #2
assertThrows(RollbackException.class,
                   () -> em.getTransaction().commit());       #3
em.close();
#1 <kbd>find()</kbd> accepts a <kbd>LockModeType</kbd>. The <kbd>OPTIMISTIC_FORCE_INCREMENT</kbd> mode tells Hibernate that the version of the retrieved <kbd>Item</kbd> should be incremented after loading, even if it’s never modified in the unit of work.
#2 The code persists a new <kbd>Bid</kbd> instance; this doesn’t affect any values of the <kbd>Item</kbd> instance. A new row is inserted into the <kbd>BID</kbd> table. Hibernate wouldn’t detect concurrently made bids without a forced version increment of the <kbd>Item</kbd>.
#3 When flushing the persistence context, Hibernate executes an <kbd>INSERT</kbd> for the new <kbd>Bid</kbd> and forces an <kbd>UPDATE</kbd> of the <kbd>Item</kbd> with a version check. If someone modified the <kbd>Item</kbd> concurrently or placed a <kbd>Bid</kbd> concurrently with this procedure, Hibernate throws an exception.

The transaction loads an Item with OPTIMISTIC_FORCE_INCREMENT, reads its highest bid, persists a new Bid, and demonstrates a failed commit after another transaction changes the record. section 11.2.2

How Java Persistence with Spring Data and Hibernate epub covers it

How deep it goes
Works through the bid transaction and its failed commit, including why ordinary Item versioning misses the conflict.
Examples
Uses simultaneous auction bids and a JPA transaction that reads the highest bid before persisting another.
What you can do after
Apply it via JPA’s OPTIMISTIC_FORCE_INCREMENT lock mode to detect concurrent bid conflicts at commit.
How it connects
Connects race conditions to optimistic concurrency control, entity versioning, aggregate roots, and pessimistic locking.

Also explained in

Compare how 20 books explain race condition →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Java Persistence with Spring Data and Hibernate epub. The section links point to the parts of the book it draws on.