liveBook · Manning

Forced version increment

As explained in Java Persistence with Spring Data and Hibernate epub

A forced version increment deliberately advances an entity version so concurrent operations involving that entity become detectable conflicts.

Placing a bid requires reading the highest bid, comparing it with a new bid, and storing the new bid if it is higher. Another transaction can place a bid between the read and the insert. Ordinary versioning of the Item does not reveal this race because placing a Bid does not modify the Item. section 11.2.2

Use `LockModeType.OPTIMISTIC_FORCE_INCREMENT` when loading the Item to force its version forward. Concurrent bidding transactions can then conflict even though they insert separate Bid entities. This is useful when changes to related data should advance the version of an aggregate root. It is unnecessary if bids are an `@ElementCollection` of Item: adding to that collection already increments the Item version. 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 Item with the force-increment lock mode, reads the highest bid, and persists a new bid. A concurrent change makes the illustrated commit fail with a rollback exception. section 11.2.2

How Java Persistence with Spring Data and Hibernate epub covers it

How deep it goes
Works through a JPA transaction and its failed commit; stops short of detailing the generated version-update SQL.
Examples
Uses competing auction bids against one Item and a JPA transaction that fails at commit.
What it assumes
Assumes familiarity with JPA entities, transactions, and versioning.
What you can do after
Apply it via JPA’s `OPTIMISTIC_FORCE_INCREMENT` lock mode to detect conflicts involving related entities.
How it connects
Connects optimistic concurrency control, entity versioning, aggregate roots, and element collections.

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.