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.
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.
Nicely explained with a good example.
ReplyDelete