Sunday 29 September 2019

Spring JPA: Optimistic Locking


While working in concurrent environment, we should make sure that the collisions will not occur while performing operations with database. We can achieve the consistency in data writes/reads using locking.

JPA support two types of locking.
a.   Optimistic Locking
b.   Pessimistic Locking

In this post, I am going to explain about optimistic locking.

To achieve optimistic locking, a table maintains a property @Version annotation. While working with,
a.   Every transaction that wants to work with this record reads the record and keep the value of version property.
b.   Whenever transaction wants to update the record, it validates the value of version property again.
c.    If the value of version property changed by some other transactions, then OptimisticLockException is thrown, else the record is updated and value of the version property gets incremented.

Optimistic locking is suitable for read centric applications, where we perform more reads than writes.

Example
@Entity
@Table(name = "employees")
public class Employee {

 @Id
 @GeneratedValue
 @Column(name = "EMPLOYEE_ID")
 private int id;

 @Column(name = "EMPLOYEE_FIRST_NAME")
 private String firstName;

 @Column(name = "EMPLOYEE_LAST_NAME")
 private String lastName;

 @Version
 private Integer version;

 ......
 ......
}

Rules to be followed while working with @Version property
a.   An entity must have exactly one version property.
b.   The Version property should be mapped to the primary table for the entity class
c.    Following types are supported for version properties.
1.   int
2.   Integer
3.   short
4.   Short
5.   long
6.   Long
7.   java.sql.Timestamp

Find the below working application.

Employee.java
package com.sample.app.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name = "employees")
public class Employee {

 @Id
 @GeneratedValue
 @Column(name = "EMPLOYEE_ID")
 private int id;

 @Column(name = "EMPLOYEE_FIRST_NAME")
 private String firstName;

 @Column(name = "EMPLOYEE_LAST_NAME")
 private String lastName;

 @Version
 private Integer version;
 
 public Employee() {
  
 }

 public Employee(String firstName, String lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
 }

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public Integer getVersion() {
  return version;
 }

 public void setVersion(Integer version) {
  this.version = version;
 }

 @Override
 public String toString() {
  return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", version=" + version
    + "]";
 }

}


EmployeeRepository.java
package com.sample.app.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.sample.app.entity.Employee;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

}


App.java
package com.sample.app;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.sample.app.entity.Employee;
import com.sample.app.repository.EmployeeRepository;

@SpringBootApplication
public class App {
 public static void main(String args[]) {
  SpringApplication.run(App.class, args);
 }

 @Bean
 public CommandLineRunner demo(EmployeeRepository empRepo) {
  return (args) -> {
   Employee emp1 = new Employee("Naresh", "Deva");
   Employee emp2 = new Employee("Nani", "Kulkarni");
   Employee emp3 = new Employee("Ram", "Kishore");
   
   empRepo.save(emp1);
   empRepo.save(emp2);
   Employee empToUpdate = empRepo.save(emp3);
   
   empRepo.findAll().forEach(System.out::println);
   
   System.out.println("\nUpdating the employee\n");
   empToUpdate.setLastName("Manohar");
   empToUpdate = empRepo.save(empToUpdate);
   System.out.println(empToUpdate);
   
   System.out.println("\nUpdating the employee again\n");
   empToUpdate.setLastName("Parikar");
   empToUpdate = empRepo.save(empToUpdate);
   System.out.println(empToUpdate);

  };
 }

}


application.properties
logging.level.root=WARN
logging.level.org.hibernate=ERROR

## H2 specific properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;

spring.datasource.username=krishna
spring.datasource.password=password123

spring.datasource.driverClassName=org.h2.Driver

## JPA specific properties
# Creates the schema, destroying previous data.
spring.jpa.hibernate.ddl-auto=create

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=false

## Database connection pooling properties
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=3


pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>springH2</groupId>
 <artifactId>springH2</artifactId>
 <version>1</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.6.RELEASE</version>
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <dependencies>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
  </dependency>
  
 </dependencies>

</project>


Total project structure looks like below.




Run App.java, you can see below messages in console.
Hibernate: drop table employees if exists
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table employees (employee_id integer not null, employee_first_name varchar(255), employee_last_name varchar(255), version integer, primary key (employee_id))
Hibernate: call next value for hibernate_sequence
Hibernate: insert into employees (employee_first_name, employee_last_name, version, employee_id) values (?, ?, ?, ?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into employees (employee_first_name, employee_last_name, version, employee_id) values (?, ?, ?, ?)
Hibernate: call next value for hibernate_sequence
Hibernate: insert into employees (employee_first_name, employee_last_name, version, employee_id) values (?, ?, ?, ?)
Hibernate: select employee0_.employee_id as employee1_0_, employee0_.employee_first_name as employee2_0_, employee0_.employee_last_name as employee3_0_, employee0_.version as version4_0_ from employees employee0_
Employee [id=1, firstName=Naresh, lastName=Deva, version=0]
Employee [id=2, firstName=Nani, lastName=Kulkarni, version=0]
Employee [id=3, firstName=Ram, lastName=Kishore, version=0]

Updating the employee

Hibernate: select employee0_.employee_id as employee1_0_0_, employee0_.employee_first_name as employee2_0_0_, employee0_.employee_last_name as employee3_0_0_, employee0_.version as version4_0_0_ from employees employee0_ where employee0_.employee_id=?
Hibernate: update employees set employee_first_name=?, employee_last_name=?, version=? where employee_id=? and version=?
Employee [id=3, firstName=Ram, lastName=Manohar, version=1]

Updating the employee again

Hibernate: select employee0_.employee_id as employee1_0_0_, employee0_.employee_first_name as employee2_0_0_, employee0_.employee_last_name as employee3_0_0_, employee0_.version as version4_0_0_ from employees employee0_ where employee0_.employee_id=?
Hibernate: update employees set employee_first_name=?, employee_last_name=?, version=? where employee_id=? and version=?
Employee [id=3, firstName=Ram, lastName=Parikar, version=2]

You can download complete working application from this link.

Previous                                                    Next                                                    Home

1 comment: