Over a million developers have joined DZone.

Hibernate Locking Patterns: How Does Optimisitic_Force_Increment Lock Mode Work

· Java Zone

Microservices! They are everywhere, or at least, the term is. When should you use a microservice architecture? What factors should be considered when making that decision? Do the benefits outweigh the costs? Why is everyone so excited about them, anyway?  Brought to you in partnership with IBM.

Introduction

In my previous post, I explained how OPTIMISTIC Lock Mode works and how it can help us synchronize external entity state changes. In this post, we are going to unravel the OPTIMISTIC_FORCE_INCREMENT Lock Mode usage patterns.

With LockModeType.OPTIMISTIC, the locked entity version is checked towards the end of the current running transaction, to make sure we don’t use a stale entity state. Because of the application-level validation nature, this strategy is susceptible to race-conditions, therefore requiring an additional pessimistic lock .

The LockModeType.OPTIMISTIC_FORCE_INCREMENT not only it checks the expected locked entity version, but it also increments it. Both the check and the update happen in the same UPDATE statement, therefore making use of the current database transaction isolation level and the associated physical locking guarantees.

It is worth noting that the locked entity version is bumped up even if the entity state hasn’t been changed by the current running transaction.

A Centralized Version Control Use Case

As an exercise, we are going to emulate a centralized Version Control System, modeled as follows:

RepositoryCommitChangeOptimisticForceIncrement

The Repository is our system root entity and each state change is represented by aCommit child entity. Each Commit may contain one or more Change components, which are propagated as a single atomic Unit of Work.

The Repository version is incremented with each new Commit. For simplicity sake, we only verify the Repository entity version, although a more realistic approach would surely check each individual file version instead (to allow non-conflicting commits to proceed concurrently).

Testing time

First, we should check if the OPTIMISTIC_FORCE_INCREMENT Lock Mode suits our use case requirements:

doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        Repository repository = (Repository) session.get(Repository.class, 1L);
        session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(repository);
        Commit commit = new Commit(repository);
        commit.getChanges().add(new Change("README.txt", "0a1,5..."));
        commit.getChanges().add(new Change("web.xml", "17c17..."));
        session.persist(commit);
        return null;
    }
});

This code generates the following output:

#Alice selects the Repository and locks it using an OPTIMISTIC_FORCE_INCREMENT LockMode
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 
 
#Alice makes two changes and inserts a new Commit
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 
Query:{[insert into commit (id, repository_id) values (default, ?)][1]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,0a1,5...,README.txt]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,17c17...,web.xml]} 
 
#The Repository version is bumped up
Query:{[update repository set version=? where id=? and version=?][1,1,0]} 

Our user has selected a Repository and issued a new Commit. At the end of her transaction, the Repository version is incremented as well (therefore recording the new Repository state change).

Conflict detection

In our next example, we are going to have two users (Alice and Bob) to concurrently commit changes. To avoid losing updates, both users acquire an explicit OPTIMISTIC_FORCE_INCREMENT Lock Mode.

Before Alice gets the chance to commit, Bob has just finished his transaction and incremented the Repository version. Alice transaction will be rolled back, throwing an unrecoverable StaleObjectStateException.

ExplicitLockingOptimisticForceIncrement

To emulate the conflict detection mechanism, we are going to use the following test scenario:

doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        Repository repository = (Repository) session.get(Repository.class, 1L);
        session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(repository);
 
        executeAndWait(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                return doInTransaction(new TransactionCallable<Void>() {
                    @Override
                    public Void execute(Session _session) {
                        Repository _repository = (Repository) _session.get(Repository.class, 1L);
                        _session.buildLockRequest(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)).lock(_repository);
                        Commit _commit = new Commit(_repository);
                        _commit.getChanges().add(new Change("index.html", "0a1,2..."));
                        _session.persist(_commit);
                        return null;
                    }
                });
            }
        });
 
        Commit commit = new Commit(repository);
        commit.getChanges().add(new Change("README.txt", "0a1,5..."));
        commit.getChanges().add(new Change("web.xml", "17c17..."));
        session.persist(commit);
        return null;
    }
});

The following output is generated:

#Alice selects the Repository and locks it using an OPTIMISTIC_FORCE_INCREMENT LockMode
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 
 
#Bob selects the Repository and locks it using an OPTIMISTIC_FORCE_INCREMENT LockMode
Query:{[select lockmodeop0_.id as id1_2_0_, lockmodeop0_.name as name2_2_0_, lockmodeop0_.version as version3_2_0_ from repository lockmodeop0_ where lockmodeop0_.id=?][1]} 
 
#Bob makes a change and inserts a new Commit
Query:{[insert into commit (id, repository_id) values (default, ?)][1]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][1,0a1,2...,index.html]} 
 
#The Repository version is bumped up to version 1
Query:{[update repository set version=? where id=? and version=?][1,1,0]} 
 
#Alice makes two changes and inserts a new Commit
Query:{[insert into commit (id, repository_id) values (default, ?)][1]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][2,0a1,5...,README.txt]} 
Query:{[insert into commit_change (commit_id, diff, path) values (?, ?, ?)][2,17c17...,web.xml]} 
 
#The Repository version is bumped up to version 1 and a conflict is raised
Query:{[update repository set version=? where id=? and version=?][1,1,0]} 
INFO  [main]: c.v.h.m.l.c.LockModeOptimisticForceIncrementTest - Failure: 
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : 
[com.vladmihalcea.hibernate.masterclass.laboratory.concurrency.LockModeOptimisticForceIncrementTest$Repository#1]

This example exhibits the same behavior as the typical implicit optimistic lockingmechanism. The only difference lies in the version change originator. While implicit locking only works for modifying entities, explicit locking can span to any managed entity instead (disregarding the entity state change requirement).

Conclusion

The OPTIMISTIC_FORCE_INCREMENT is therefore useful for propagating a child entity state change to an unmodified parent entity. This pattern can help us synchronize various entity types, by simply locking a common parent of theirs.

When a child entity state change has to trigger a parent entity version incrementation, the explicit OPTIMISTIC_FORCE_INCREMENT lock mode is probably what you are after.

Code available on GitHub.


Discover how the Watson team is further developing SDKs in Java, Node.js, Python, iOS, and Android to access these services and make programming easy. Brought to you in partnership with IBM.

Topics:

Published at DZone with permission of Vlad Mihalcea, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}