In this article, we will see @Version Annotation Example In Hibernate Using Spring Boot. We will see the complete example from scratch but before going ahead let’s see below points which demonstrate how @Version behaves in a different scenario.
Hibernate version default value – When we create an entity for the first time default version value is zero(the field which is annotated with @Version), which hibernate takes care.
When does hibernate version increase – When we modify some entity’s field and update that entity the version will increase. As many time we will update the entity version will increase, but at least one field should be changed. If we don’t modify any field and try to update the entity, the version will not update.
What will happen if don’t provide correct version – Suppose we are trying to update an entity and we are not providing the correct version(for example in database version value is 1 and we are providing version value 0 or 2 any number rather than 1) we will get an exception.
“optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction.”
Note – If we don’t provide the latest version it will throw an exception and we will not able to perform the update operation.
How to use @Version annotation with the field – See below sample code.
@Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name = "student_name") private String studentName; @Column(name = "roll_number") private String rollNumber; @Column(name = "version") @Version private Long version; }
When we create an entity for the first time default version value will be zero(we will see in the example later). Now we want to update this entity(modifying some field) we need to pass the same version number and our version should increase by +1. For example, consider we have an entity called Student which has four field id, studentName, rollNumber and version. If we modified one or more fields(i.e studentName or rollNumber ) of the entity and try to update this entity, the version will increase by +1. The @Version annotation in hibernate is used for Optimistic locking while performing update operation. In the Optimistic Locking approach first, we read a record from the database(this record contains the version number) and the user updates this record which we have just read from the database(suppose the user has modified one field in entity and try to update that entity). In this case, User will only able to update the record if the version hasn’t been changed by another user before you write the record back into the database.
To see how version works( in case of save and update) we will define two REST API as below.
http://localhost:9091/student/save – default version 0.
http://localhost:9091/student/update/1 – version will increase by 1. At least one field must modify else version will remain the same.
Let’s see @Version Annotation Example In Hibernate Using Spring Boot, Eclipse and Oracle from scratch.
Open eclipse and create maven project, Don’t forget to check ‘Create a simple project (skip)’ click on next. Fill all details(GroupId – versionexample, ArtifactId – versionexample and name – versionexample) and click on finish. Keep packaging as the jar.
Modify 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>versionexample</groupId> <artifactId>versionexample</artifactId> <version>0.0.1-SNAPSHOT</version> <name>versionexample</name> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <fork>true</fork> <executable>C:\Program Files\Java\jdk1.8.0_131\bin\javac.exe</executable> </configuration> </plugin> </plugins> </build> </project>
Note – In pom.xml we have defined javac.exe path in configuration tag. You need to change accordingly i.e where you have installed JDK.
If you see any error for oracle dependency then follow these steps.
Directory structure –
Student.java
package com.javatute.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Version; @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name = "student_name") private String studentName; @Column(name = "roll_number") private String rollNumber; @Column(name = "version") @Version private Long version; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public Long getVersion() { return version; } public void setVersion(Long version) { this.version = version; } public String getRollNumber() { return rollNumber; } public void setRollNumber(String rollNumber) { this.rollNumber = rollNumber; } }
StudentController.java
package com.javatute.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.javatute.entity.Student; import com.javatute.service.StudentService; @RestController @RequestMapping(value = "/student") public class StudentController { @Autowired private StudentService studentService; @RequestMapping(value = "/save", method = RequestMethod.POST) @ResponseBody public Student saveBook(@RequestBody Student student) { Student studentResponse = (Student) studentService.saveStudent(student); return studentResponse; } @RequestMapping(value = "/update/{id}", method = RequestMethod.PUT) @ResponseBody public Student saveBook(@RequestBody Student student, @PathVariable int id) { Student existingStudent = studentService.findById(id); if(existingStudent == null) { throw new RuntimeException("Record not found"); } Student studentResponse = (Student) studentService.updateStudent(student); return studentResponse; } }
StudentRepository.java – interface
package com.javatute.repository; import java.io.Serializable; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; import com.javatute.entity.Student; @Repository public interface StudentRepository extends CrudRepository<Student,Serializable> { public Student findById(int id); }
StudentService.java – interface
package com.javatute.service; import org.springframework.stereotype.Component; import com.javatute.entity.Student; @Component public interface StudentService { public Student saveStudent(Student student); public Student findById(int id); public Student updateStudent(Student student); }
StudentServiceImpl.java
package com.javatute.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.javatute.entity.Student; import com.javatute.repository.StudentRepository; import com.javatute.service.StudentService; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Service("studentServiceImpl") public class StudentServiceImpl implements StudentService { @Autowired private StudentRepository studentRepository; @Transactional public Student saveStudent(Student student) { Student studentresponse = studentRepository.save(student); return studentresponse; } @Transactional public Student findById(int id) { Student studentresponse = studentRepository.findById(id); return studentresponse; } @Transactional public Student updateStudent(Student student) { Student studentresponse = studentRepository.save(student); return studentresponse; } }
SpringMain.java
package com.javatute.main; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan(basePackages = "com.*") @EntityScan("com.javatute.entity") public class SpringMain { public static void main(String[] args) { SpringApplication.run(SpringMain.class, args); } }
JpaConfig.java
package com.javatute.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @Configuration @EnableJpaRepositories(basePackages = "com.javatute.repository") public class JpaConfig { }
application.properties
# Connection url for the database spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE spring.datasource.username=SYSTEM spring.datasource.password=oracle2 spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver # Show or not log for each sql query spring.jpa.show-sql = true spring.jpa.hibernate.ddl-auto =create spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect server.port = 9091
Let’s run the SpringMain class(run as java application).
Perform save operation first using below REST API.
http://localhost:9091/student/save
Request Data –
{ "studentName":"rakesh", "rollNumber":"0126CS01" }
Response Data –
{ "id": 1, "studentName": "rakesh", "rollNumber": "0126CS01", "version": 0 }
In response, we have version value is zero. When we create entity first time the default value of the version is zero. Now we will update the entity. Just we will change the name or rollNumber and we will see what does happen.
For update the entity we will use below REST API. Let’s update the rollNumber of the student.
http://localhost:9091/student/update/1
Request Data –
{ "id": 1, "studentName": "rakesh", "rollNumber": "0126CS02", "version": 0 }
Response Data –
{ "id": 1, "studentName": "rakesh", "rollNumber": "0126CS02", "version": 1 }
Now version has been increased by +1. This is how @Version works in Hibernate. As many times we will update the student’s name or rollNumber version will increase by 1.
Now, what will happen if we want to update student entity but we don’t provide proper version? For example we want to update name too. Here what would be the version in request data? It would be 1 or 0? When we are going to update entity for next time version should be current version i.e nothing but 1.
Valid Request Data –
{ "id": 1, "studentName": "rakesh", "rollNumber": "0126CS02", "version": 1 }
Again the question comes what will happen if we try to update student entity with old version i.e 0(update rollNumber to 0126CS03)
Request data –
{ "id": 1, "studentName": "rakesh", "rollNumber": "0126CS03", "version": 0 }
Response –
{ "timestamp": "2020-01-11T18:12:33.690+0000", "status": 500, "error": "Internal Server Error", "message": "Object of class [com.javatute.entity.Student] with identifier [1]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.javatute.entity.Student#1]", "path": "/student/update/1" }
Oops ! We have exception
Note – When we create a new entity default version assign to zero by hibernate as below.
Versioning.java
private static Object seed(VersionType versionType, SharedSessionContractImplementor session) { final Object seed = versionType.seed( session ); return seed; }
LongType.java
@Override public Long seed(SharedSessionContractImplementor session) { return ZERO; }
That’s all about @Version Annotation Example In Hibernate Using Spring Boot.
You may like.
- Hibernate Validator Constraints Example Using Spring Boot
- Hibernate Table Per Concrete Class Spring Boot
- Hibernate Table Per Subclass Inheritance Spring Boot
- Hibernate Single Table Inheritance using Spring Boot
- Many To Many Mapping Annotation Example In Hibernate/JPA Using Spring Boot And Oracle
- One To Many Bidirectional Mapping In Hibernate/JPA Annotation Example Using Spring Boot and Oracle
- Many To One Unidirectional Mapping In Hibernate/JPA Annotation Example Using Spring Boot and Oracle
- @Temporal Annotation Example In Hibernate/Jpa Using Spring Boot
See more about @Version. Here is the docs.