Optimistic Locking Versioning in Hibernate
This tutorial explains Optimistic Locking, simulates the problem and then solves the problem using Optimistic Locking Versioning in Hibernate.
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 |
<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.hibernate</groupId> <artifactId>hibernate-basic</artifactId> <version>1.0</version> <packaging>pom</packaging> <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>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </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. Hibernate Entity Class & Hibernate Mapping File
Lets us create a Hibernate 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
package com.heapcode.hibernate.entity; import java.io.Serializable; import java.util.Date; /** * @author Manjunath Sampth * */ public class Ticket implements Serializable{ private static final long serialVersionUID = 1932969105791275898L; public Ticket(){} private int ticketIdPk; private String ticketName; private int createdBy; private Date createdDate; private int modifiedBy; private Date modifiedDate; public int getTicketIdPk() { return ticketIdPk; } public void setTicketIdPk(int ticketIdPk) { this.ticketIdPk = ticketIdPk; } public String getTicketName() { return ticketName; } public void setTicketName(String ticketName) { this.ticketName = ticketName; } public int getCreatedBy() { return createdBy; } public void setCreatedBy(int createdBy) { this.createdBy = createdBy; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } public int getModifiedBy() { return modifiedBy; } public void setModifiedBy(int modifiedBy) { this.modifiedBy = modifiedBy; } public Date getModifiedDate() { return modifiedDate; } public void setModifiedDate(Date modifiedDate) { this.modifiedDate = modifiedDate; } } |
and its appropriate Hibernate Mapping File Ticket.hbm.xml as
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 |
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.heapcode.hibernate.entity"> <class catalog="heapcode" name="Ticket" table="TICKET"> <id name="ticketIdPk" type="java.lang.Integer"> <column name="TICKET_ID_PK" not-null="true" unique="true"/> <generator class="identity"/> </id> <property generated="never" lazy="false" name="ticketName" type="java.lang.String"> <column length="10" name="TICKET_NAME" not-null="true"/> </property> <property generated="never" lazy="false" name="createdBy" type="java.lang.Integer"> <column name="CREATED_BY" not-null="true"/> </property> <property generated="never" lazy="false" name="createdDate" type="java.util.Date"> <column length="4" name="CREATED_DATE" not-null="true"/> </property> <property generated="never" lazy="false" name="modifiedBy" type="java.lang.Integer"> <column name="MODIFIED_BY" not-null="true"/> </property> <property generated="never" lazy="false" name="modifiedDate" type="java.util.Date"> <column length="4" name="MODIFIED_DATE" not-null="true"/> </property> </class> </hibernate-mapping> |
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 44 45 46 |
package com.heapcode.hibernate.main; import java.util.Date; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import com.heapcode.hibernate.entity.Ticket; /** * @author Manjunath Sampath * */ public class HibernateMain { public static void main(String[] args) { SessionFactory sf = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory(); /* John Logs in and Selects a ticket*/ Session johnSession = sf.openSession(); Transaction johnTx = johnSession.getTransaction(); johnTx.begin(); Ticket johnTicket = (Ticket) johnSession.load(Ticket.class, 10); johnTicket.setTicketName("John's Ticket"); johnTicket.setModifiedBy(1); johnTicket.setModifiedDate(new Date()); johnSession.saveOrUpdate(johnTicket); /* Before John Books (commits) Tom also logs in and selects the same ticket and books it (commits)*/ Session tomSession = sf.openSession(); Transaction tomTx = tomSession.getTransaction(); tomTx.begin(); Ticket tomTicket = (Ticket) tomSession.load(Ticket.class, 10); tomTicket.setTicketName("Tom's Ticket"); tomTicket.setModifiedBy(1); tomTicket.setModifiedDate(new Date()); tomSession.saveOrUpdate(tomTicket); tomTx.commit(); tomSession.close(); /* John now books and confirms the ticket*/ johnTx.commit(); johnSession.close(); sf.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 |
private int optLock; public int getOptLock() { return optLock; } public void setOptLock(int optLock) { this.optLock = optLock; } |
and modify the Ticket.hbm.xml too by adding the below line.
1 |
<version name="optLock" column="OPT_LOCK" generated="never" type="integer" /> |
The above entry does the trick. On running the Main Program HibernateMain.java described above, we get the below Console Output.
5. Project Structure
Download Optimistic Lock & Versioning in Hibernate 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 Session,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.