Spring Boot2+JPA Pessimistic Lock and Optimistic Lock Practical Tutorial

  • 2021-11-29 06:57:45
  • OfStack

Directory preface pessimistic lock and concurrency using SQL for update to solve concurrency problem using JPA @ Lock line lock annotation to solve concurrency problem If it is @ NameQuery, you can use optimistic lock and concurrency using version field to solve concurrency problem using JPA @ Version version mechanism to solve concurrency problem. When to use pessimistic lock or optimistic lock

Preface

A large number of requests, or simultaneous operations, easily lead to concurrency in the business of the system. Usually when it comes to concurrency, the solution is nothing more than the front-end limit repeated submission, background pessimistic lock or optimistic lock limit.

Pessimistic Lock and Concurrency

Pessimistic lock (Pessimistic Lock), as its name implies, is very pessimistic. Every time you take data, you think others will modify it, so you will lock it every time you take data, so that others will block until they unlock it, which can be understood as an exclusive lock. In java, synchronized and ReentrantLock reentry locks are pessimistic locks, and table locks, row locks, read-write locks and so on in databases are also pessimistic locks.

Using for update of SQL to solve concurrency problem

Row lock is to lock this 1 row of data when operating data. Other threads must wait to read and write, but other data in the same table can still be operated by other threads. As long as for update is added after sql to be queried, the query row can be locked. Special attention should be paid to the fact that the query condition must be an index column. If it is not an index, it will become a table lock, locking the whole table.


public interface ArticleRepository extends JpaRepository<Article, Long> {
    @Query(value = "select * from article a where a.id = :id for update", nativeQuery = true)
    Optional<Article> findArticleForUpdate(Long id);
}

Using @ Lock line lock annotation of JPA to solve concurrency problem

If for update is too primitive, JPA provides a more elegant method, that is, @ Lock annotation.

Add the lock method of JPA to Repository, where the LockModeType.PESSIMISTIC_WRITE parameter is the row lock.

For the LockModeType type, you can find the document https://docs.oracle.com/javaee/7/api/javax/persistence/LockModeType.html here

NONE : No lock. OPTIMISTIC : Optimistic lock. OPTIMISTIC_FORCE_INCREMENT : Optimistic lock, with version update. PESSIMISTIC_FORCE_INCREMENT : Pessimistic write lock, with version update. PESSIMISTIC_READ : Pessimistic read lock. PESSIMISTIC_WRITE : Pessimistic write lock. READ : Synonymous with OPTIMISTIC. WRITE : Synonymous with OPTIMISTIC_FORCE_INCREMENT.

public interface ArticleRepository extends JpaRepository<Article, Long> { 
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select a from Article a where a.id = :id")
    Optional<Article> findArticleWithPessimisticLock(Long id);
}

If it is @ NameQuery, you can


@NamedQuery(name="lockArticle",query="select a from Article a where a.id = :id",lockMode = PESSIMISTIC_READ)
public class Article

If you use entityManager, you can set LocakMode:


 Query query = entityManager.createQuery("from Article where articleId = :id");
 query.setParameter("id", id);
 query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
 query.getResultList();

Optimistic Lock and Concurrency

Optimistic lock (Optimistic Lock), as its name implies, is very optimistic. Every time you get the data, you think that others will not modify it, so you will not lock it. However, when you submit the update, you will judge whether others have modified it during this period. Therefore, pessimistic lock restricts other threads, while optimistic lock restricts itself. Although his name has a lock, it is not actually locked. It is usually version version number mechanism and CAS algorithm.

Using version field to solve concurrency problem

The version number mechanism is to add a field version to the database as the version number. Then get Article with a version number, such as version=1, and then you operate on this Article1 wave, which will be inserted into the database after operation.

Verify the version number of version under 1, and find that version=2 corresponding to Article records in the database, which is different from the version in my hand. It shows that the submitted Article is not the latest, so you can't get update to the database, and report errors, so as to avoid the problem of data conflict when concurrent.


public interface ArticleRepository extends JpaRepository<Article, Long> {
    @Modifying
    @Query(value = "update article set content= :content, version = version + 1 where id = :id and version = :version", nativeQuery = true)
    int updateArticleWithVersion(Long id, String content, Long version);
}

public void postComment(Long articleId, String content) {
 //get article
    Optional<Article> articleOptional = articleRepository.findById(articleId);
    //update with Optimistic Lock
    int count = articleRepository.updateArticleWithVersion(article.getId(), content, article.getVersion());
  
    if (count == 0) {
        throw new RuntimeException(" Failed to update data , Please refresh and try again ");
    }else{
     articleRepository.save(article);
    } 
}

Using @ Version Version Mechanism of JPA to Solve Concurrency Problems

Is there a more elegant way? Of course, there must be, that is, the @ Version mode that comes with JPA implements optimistic locking.

each entity class must have only one version attribute. Each entity class can have only one @ Version field, not more it must be placed in the primary table for an entity mapped to several tables. Entities that map to more than one table must be placed in the main table type of a version attribute must be one of the following: int, Integer, long, Long, short, Short, java.sql.Timestamp ,

The types supported by @ Version must be the following:

int Integer OPTIMISTIC0 Long short Short java.sql.Timestamp

First add the @ Version annotation to the version field of the Article entity class


@Data
@Entity
public class Article{ 
    @Id
    private Long id;  
  //......  
    @Version
    private Integer version; 
}

Article article = entityManager.find(Article.class, id);
entityManager.lock(article , LockModeType.OPTIMISTIC);
entityManager.refresh(article , LockModeType.READ);

When to use a pessimistic lock or an optimistic lock

Pessimistic locks are suitable for writing more and reading less. Because the thread will monopolize this resource when using it, it is suitable to use pessimistic lock. Otherwise, if users just browse articles, they will often lock with pessimistic lock, which increases the resource consumption of locking and unlocking.

Optimistic locks are suitable for writing less and reading more. Because optimistic locks will be rolled back or retried when they conflict, if the number of written requests is large, conflicts will often occur, and there will be frequent rollback and retry combined with transactions, which consumes a lot of system resources.

Therefore, pessimistic locks and optimistic locks are not absolutely good or bad, and we must decide which one to use according to the specific business situation. In addition, it is also mentioned in Alibaba's development manual:

If the probability of each access violation is less than 20%, optimistic locking is recommended, otherwise pessimistic locking is used. The number of retries of optimistic locks must not be less than 3.

Alibaba suggests that the value of 20% conflict probability should be used as the dividing line to decide whether to use optimistic lock and pessimistic lock. Although this value is not absolute, it is also a good reference for Alibaba bosses to sum up.


Related articles: