Optimistic Locking Versioning in JPA (Annotations)
This tutorial explains Optimistic Locking, simulates the problem and then solves the problem using Optimistic Locking Versioning in Hibernate using JPA Annotations.
1. Definition
The standard Definition for Optimistic Locking is
“Optimistic locking assumes that multiple transactions can complete without affecting each other, and that therefore transactions can proceed without locking the data resources that they affect. Before committing, each transaction verifies that no other transaction has modified its data. If the check reveals conflicting modifications, the committing transaction rolls back.”
Lets us consider a simple sequence of events to explain the above definition.
2. Scenario
Scenario: Lets say there are two people John & Tom who want to book a ticket for an Event and there is only one ticket left.
1. John logs in and selects the only available ticket say Ticket 10 and hasn’t booked it yet.
2. Tom logs in and selects the same Ticket 10 and quickly books it and gets a confirmation.
3. John also now clicks on Book and he too is confirmed the same ticket.
The above scenario can be avoided using Optimistic Locking.
3. Simulation
Lets simulate the above in our Code.
a. Project Setup
Lets create a simple Maven project with the below pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<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>com.heapcode.jpa</groupId> <artifactId>jpa-basic</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>jpa-basic</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>3.6.10.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.6.10.Final</version> </dependency> </dependencies> </project> |
In the above pom.xml we added dependencies for Hibernate 3.6.10, MySQL Connector 5.1.25, javassist and sl4j for logging.
b. Database Table Creation
1 2 3 4 5 6 7 8 9 10 |
CREATE TABLE `TICKET` ( `TICKET_ID_PK` int(10) unsigned NOT NULL AUTO_INCREMENT, `TICKET_NAME` varchar(45) NOT NULL, `CREATED_BY` int(10) unsigned NOT NULL, `CREATED_DATE` datetime NOT NULL, `MODIFIED_BY` int(10) unsigned NOT NULL, `MODIFIED_DATE` datetime NOT NULL, PRIMARY KEY (`TICKET_ID_PK`), UNIQUE KEY `TICKET_ID_PK_UNIQUE` (`TICKET_ID_PK`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1$$ |
c. JPA Entity Class
Lets us create a JPA Entity Class Ticket.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package com.heapcode.jpa.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * @author Manjunath Sampth * */ @Entity @Table(name = "TICKET", catalog = "heapcode") public class Ticket extends BaseEntity implements Serializable{ private static final long serialVersionUID = 1932969105791275898L; public Ticket(){} private int ticketIdPk; private String ticketName; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "TICKET_ID_PK", unique = true, nullable = false) public int getTicketIdPk() { return ticketIdPk; } public void setTicketIdPk(int ticketIdPk) { this.ticketIdPk = ticketIdPk; } @Column(name = "TICKET_NAME", nullable = false, length = 45) public String getTicketName() { return ticketName; } public void setTicketName(String ticketName) { this.ticketName = ticketName; } } |
d. Main Program
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package com.heapcode.jpa.main; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Persistence; import com.heapcode.jpa.entity.Ticket; public class JPAMain { /** * @param args */ public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("HEAPCODE"); /* John Logs in and Selects a ticket*/ EntityManager johnEntityManager = emf.createEntityManager(); EntityTransaction johnTx = johnEntityManager.getTransaction(); johnTx.begin(); Ticket johnTicket = johnEntityManager.find(Ticket.class, 10); johnTicket.setTicketName("John's Ticket"); johnEntityManager.merge(johnTicket); /* Before John Books (commits) Tom also logs in and selects the same ticket and books it (commits)*/ EntityManager tomEntityManager = emf.createEntityManager(); EntityTransaction tomTx = tomEntityManager.getTransaction(); tomTx.begin(); Ticket tomTicket = tomEntityManager.find(Ticket.class, 10); tomTicket.setTicketName("Tom's Ticket"); tomEntityManager.merge(tomTicket); tomTx.commit(); tomEntityManager.close(); /* John now books and confirms the ticket*/ johnTx.commit(); johnEntityManager.close(); emf.close(); } } |
e. Output
From the above Console Output we could see that both the Update Statements were executed successfully. (Both John & Tom booked the same Ticket successfully.)
4. Fix
In order to fix the above issue lets add a new column to the TICKET table using the below ALTER statement.
1 |
ALTER TABLE `heapcode`.`TICKET` ADD COLUMN `OPT_LOCK` INT NOT NULL AFTER `MODIFIED_DATE` ; |
After executing the above statement TICKET Table now is like.
We shall also modify out Ticket.java appropriately by adding a new field with getters and setters.
1 2 3 4 5 6 7 8 9 10 11 |
private int optLock; @Version @Column(name="OPT_LOCK", nullable=false) public int getOptLock() { return optLock; } public void setOptLock(int optLock) { this.optLock = optLock; } |
In the above getter the annotation @Version does the trick.
On running the Main Program JPAMain.java described above, we get the below Console Output.
5. Project Structure
Download Optimistic Lock & Versioning in JPA Example
Note: The above described TICKET table is designed in that way just for understanding the concept in a simple way, a RealWorld Ticket Booking Database Table wont be designed so. Also the variable naming conventions used for EntityManager,Ticket and Transaction are named for better/easier understanding.
I hope this has been useful for you and I’d like to thank you for reading. If you like this article, please leave a helpful comment and share it with your friends.