Why Locking is
required?
When two concurrent users try to update database row simultaneously,
there are absolute chances of losing data integrity. Locking comes in picture to
avoid simultaneous updates and ensure data integrity.
Types of Locking
There are two types of locking, Optimistic and Pessimistic. In this post optimistic locking is described with example.
There are two types of locking, Optimistic and Pessimistic. In this post optimistic locking is described with example.
- Optimistic Locking: This locking is applied on transaction commit. If entity (database row) is fetched by two users simultaneously, where first user updates and commits the row and second user tries to update row with an old version. Second user will get an exception as row is already updated and version is changed. A version number is associated for entity object to achieve optimistic locking. Whenever entity object is changed, its version number is automatically increased by one. If any transaction is performed on older version of entity, transaction is failed and application throws optimistic locking exception.
- Pessimistic Locking: Pessimistic locking explicitly restricts shared resources until the transaction completed. To get more detail about pessimistic locking refer this link .
How to Implement Optimistic Locking in JPA 2.0?
Let’s take an example; there are two Tables Company and Employee:
Table: Company
|
||||
Company_Id
|
Company_Name
|
Address
|
Survey_Sequnce
|
Version
|
1
|
AirTel
|
xyz
|
110
|
20
|
2
|
Reliance
|
abc
|
230
|
40
|
Table: Employee
|
|||
Employee_Id
|
Employee_Name
|
Company_Id
|
Employee_Survey_No
|
1
|
Narendra
|
1
|
111
|
2
|
Vinay
|
1
|
112
|
3
|
Ranu
|
2
|
231
|
4
|
Rinku
|
2
|
232
|
Whenever, employee joins the company, he is
asked to complete the company specific survey and after completion of the
survey, 'Employee_Survey_No' is generated based on SURVEY_SEQUENCE from
company table. To generate the survey number below steps are performed:
- Read the current sequence value specific to a company from company table (SURVEY_SEQUENCE)
- Increment the sequence count by 1
- Update the incremented value in company table (SURVEY_SEQUENCE)
- Use this incremented value to log into employee table as 'Employee_Survey_No'
In this use case, there are absolute chances that two parallel surveys
are performed for same company, where two employees get same survey sequence. This
scenario can be handled with the help of optimistic locking in JPA to avoid sequence
update collisions for same company.
Let’s implement optimistic locking in JPA. JPA facilitates easy
approach to handle optimistic locking.
Define the company entity and annotate version column property with @Version to enable optimistic locking for
entity.
@Entity
@Table(name = "Company", schema = "MyDB")
public class Company {
private Long companyId;
private String companyName;
private String address;
private Long surveySequence;
private Long version;
@Id
@SequenceGenerator(name = "company_seq", sequenceName = "MyDB.company_seq")
@GeneratedValue(generator = "company_seq")
@Column(name = "COMPANY_ID", unique = true, nullable = false)
public Long getCompanyId() {
return companyId;
}
public void setCompanyId(Long companyId) {
this.companyId = companyId;
}
@Column(name = "COMPANY_NAME")
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
@Column(name = "ADDRESS")
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Column(name = "SURVEY_SEQUENCE")
public Long getSurveySequence() {
return surveySequence;
}
public void setSurveySequence(Long surveySequence) {
this.surveySequence = surveySequence;
}
@Column(name = "VERSION_ID")
@Version
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
|
Now create the company DAO method to fetch the survey
sequence, increment and update incremented value into company table. As we have
annotated company table column with @Version, JPA will take care about parallel
company entity updates. If any transaction is performed with older version of
entity, transaction is failed and this method will throw ‘OptimisticLockingFailureException’.
@Repository("companyDAO")
public class CompanyDAO {
public Long
getNextSurveySequence(Long companyId) {
//Fetch company entity based on companyId
Company company = entityManager.findById(Company.class,
companyId);
// Get the current survey sequence
Long surveySeq = company.getSurveySequence();
// Increment current sequence by 1
Long nextSurveySeq
= surveySeq + 1L;
// Set new sequence in company entity
Company.setSurveySequence(nextSurveySeq)
// Update company with new sequence
entityManager.merge(company);
return nextSurveySeq;
}
}
|
Now create the service method to fetch the next survey
sequence and handle optimistic locking exception. With maximum try count
approach, you can retry data base operation for failed transactions.
@Service("companyService")
public class CompanyService {
public Long getNextSequenceForOrgId(Long companyId) throws Exception {
Long surveySequence = null;
//If any transaction is performed on older version of entity,
//transaction is failed and application throws the ‘OptimisticLockException’
for (int maxTryCount = 1; maxTryCount <= 3; maxTryCount++)
{
try {
surveySequence = companyDao.getNextSurveySequence(companyId);
break;
} catch (OptimisticLockingFailureException e) {
if (maxTryCount == 3) {
throw new Exception(e);
}
}
}
return surveySequence;
}
}
|