Enabling Transaction Locks in Spring Data JPA
Last Updated :
22 Aug, 2024
Transaction locks in Spring Data JPA can help manage concurrent data access. It can ensure the data consistency. By controlling how transactions acquire the locks on the database rows or tables, we can prevent issues such as lost updates and ensure that the application maintains data integrity. This article will guide you through enabling the transaction locks using Spring Data JPA with practical examples and explanations.
Transaction locks are crucial for managing concurrent access to the data in the database. It ensures data integrity and consistency when multiple transactions interact with the same data. In Spring Data JPA, two primary types of locks are used: Pessimistic Locking and Optimistic Locking. Each approach has its own use cases, benefits, and drawbacks.
1. Pessimistic Locking
Pessimistic Locking assumes that conflicts will occur and locks the data to prevent the other transactions from accessing it concurrently. This type of locking can be useful in scenarios where you can expect high contention for the same data or when transactions involve complex operations that should not be implemented.
How It Works
1. Acquire Lock:
- When the transaction reads a record with the pessimistic lock, the database acquires the lock on that record.
- The lock can be of the different types, such as PESSIMISTIC_READ (prevents other transactions from writing) or PESSIMISTIC_WRITE (prevents other transactions from reading or writing).
2. Block Other Transactions
- Other transactions trying to access the same record will be blocked until the lock can be released. This prevents data inconsistency caused by the concurrent modifications.
3. Release Lock
- The lock is held for the duration of the transaction and it can be automatically released when the transaction commits or rolls back.
Implementation in Spring Data JPA
In Spring Data JPA, we can implement the pessimistic locking using @Lock annotation with LockModeType.PESSIMISTIC_WRITE or LockModeType.PESSIMISTIC_READ on the repository method.
Example:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import javax.persistence.LockModeType;
import java.util.Optional;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdForUpdate(Long id);
}
Explanation:
- @Lock(LockModeType.PESSIMISTIC_WRITE): It can acquires the write lock on the record when it is read.
- findByIdForUpdate(Long id): This method can retrieves the product by its ID and locks it for writing.
Use Case
Support the two users are trying to update the price of the same product simultaneously. Pessimistic locking will ensure that only one user can be modify the record at the time preventing data inconsistency.
2. Optimistic Locking
Optimistic Locking assumes that conflicts are rare and allows the concurrent transactions to proceed without locking the data. It uses the version field to detect the conflicts and ensure that updates do not overwrite the each other.
How It Works
- Version Field: Each entity has the version field and typically annotated with @Version. This field can be used to track changes to the entity.
- Check Version: When the transaction updates the record, it includes the current version of the record. The database can checks if the version in the transaction matches the version in the database. If they can match then the update proceeds.
- Detect Conflicts: If another transaction has updated the record and changed the version, the current transaction with fail with an OptimisticLockException. It indicates the conflict that needs to be resolved.
- Handle Exceptions: The application must handle the OptimisticLockException by retrying the transaction or informing the user of the conflict.
Implementation in Spring Data JPA
In Spring Data JPA, we can optimistic locking is implemented using the @Version annotation on the field in the entity class.
Example:
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
@Version
private Long version;
}
Explanation:
- @Version: It makes the version field used for the optimistic locking.
- The version field can be automatically updated by the Hibernate on the each update.
Use Case
If two users try to update the price of the same product simultaneously, optimistic locking will be detect the conflict and the prevent one of the updates from being applied, ensuring that the most recent version of the data is used.
Implementation of Enabling Transaction Locks in Spring Data JPA
Step 1: Create a New Spring Boot project
Create a new Spring Boot Project using IntelliJ Idea. Choose the below options:
- Name: transaction-locks-demo
- Language: Java
- Type: Maven
- Packaging: Jar
Click on the Next button.
Step 2: Add the dependencies
Add the following dependencies into the Spring Boot project.
Step 3: Project Structure
Once created the project, the file structure looks like the below image.
Step 4: Configure the Application Properties
Open the application.properties file and add the following configuration of MySQL into the Spring Boot Project.
spring.application.name=transaction-locks-demo
spring.datasource.url=jdbc:mysql://localhost:3306/transaction_locks
spring.datasource.username=root
spring.datasource.password=mypassword
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
Step 5: Create the Product Entity
The Product entity class can defines the entity with the both pessimistic and optimistic locking.
Java
package com.gfg.transactionlocksdemo;
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
@Version
private Long version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
Step 6: Create the ProductRepositoy Interface
This repository interface for accessing the Product entities with methods for the pessimistic locking.
Java
package com.gfg.transactionlocksdemo;
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdForUpdate(Long id);
}
Step 7: Create the ProductService Class
This ProductService layer class with methods to demonstrates both the pessimistic and optimistic locking.
Java
package com.gfg.transactionlocksdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void updateProductPricePessimistic(Long id, Double newPrice) {
Optional<Product> product = productRepository.findByIdForUpdate(id);
product.ifPresent(p -> {
p.setPrice(newPrice);
productRepository.save(p);
});
}
@Transactional
public void updateProductPriceOptimistic(Long id, Double newPrice) {
Optional<Product> product = productRepository.findById(id);
product.ifPresent(p -> {
p.setPrice(newPrice);
productRepository.save(p);
});
}
@Transactional
public Product saveProduct(Product product) {
return productRepository.save(product);
}
}
Step 8: Create the ProductController Class
Create the controller class named as ProductController, it will handle the HTTP requests to update the product price using the both pessimistic and optimistic locking mechanisms.
Java
package com.gfg.transactionlocksdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
// Endpoint for saving a new product
@PostMapping
public Product saveProduct(@RequestBody Product product) {
return productService.saveProduct(product);
}
// Endpoint for pessimistic locking
@PutMapping("/pessimistic/{id}")
public String updateProductPricePessimistic(@PathVariable Long id, @RequestParam Double newPrice) {
productService.updateProductPricePessimistic(id, newPrice);
return "Product price updated with pessimistic lock.";
}
// Endpoint for optimistic locking
@PutMapping("/optimistic/{id}")
public String updateProductPriceOptimistic(@PathVariable Long id, @RequestParam Double newPrice) {
productService.updateProductPriceOptimistic(id, newPrice);
return "Product price updated with optimistic lock.";
}
}
Step 9: Main Class
No changes are required in the main class.
Java
package com.gfg.transactionlocksdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TransactionLocksDemoApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionLocksDemoApplication.class, args);
}
}
pom.xml file:
XML
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gfg</groupId>
<artifactId>transaction-locks-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>transaction-locks-demo</name>
<description>transaction-locks-demo</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step 10: Run the application
Once completed the project, it will start and run at port 8080.
Step 11: Testing the Application
1. Save the Products
POST http://localhost:8080/products
Output:
2. Pessimistic Lock Endpoint
PUT http://localhost:8080/products/pessimistic/1?newPrice=1300.0
Output:
This request will update the price of the product with id 1 to 1300.0 using the pessimistic locking. It ensures that the product record is locked for the duration of the transaction to prevent the concurrent modifications.
3. Optimistic Lock Endpoint
PUT http://localhost:8080/products/optimistic/1?newPrice=1400.0
Output:
This request will update the price of product with id 1 to 1400.0 using the optimistic locking. It uses the version field to detect concurrent the updates and handle conflicts.
Similar Reads
Programmatic Transaction Management in Spring
Programmatic Transaction Management in Spring provides a more flexible and customizable approach compared to declarative transaction management. Instead of using annotations or XML configurations, programmatic transaction management involves managing transactions explicitly in the code. This approac
6 min read
What is Spring Data JPA?
Spring Data JPA is a powerful framework that simplifies database access in Spring Boot applications by providing an abstraction layer over the Java Persistence API (JPA). It enables seamless integration with relational databases using Object-Relational Mapping (ORM), eliminating the need for boilerp
6 min read
Show SQL from Spring Data JPA/Hibernate in Spring Boot
In Spring Boot, Spring Data JPA is part of the larger Spring Data Project that can simplify the development of the data access layers in the spring applications using the Java Persistence API and it can provide a higher-level abstraction over the JPA API. It can reduce the boilerplate code and make
6 min read
Transaction Propagation and Isolation in Spring @Transactional Annotation
In a Spring application, the @Transactional annotation provides a declarative approach to managing transactions. It controls the behavior of transactions within the methods, ensuring that operations are either completed successfully or rolled back in case of failure. Understanding transaction propag
6 min read
Disable @EnableScheduling on Spring Tests
In Spring applications, the @EnableScheduling annotation is commonly used to enable the execution of scheduled tasks. However, during testing, it may be necessary to disable this feature to avoid unwanted side effects or to control the test execution environment. This article will guide you through
5 min read
Spring Data JPA Tutorial
In this Spring Data JPA Tutorial, youâll learn how to manage databases in your Java applications easily. Spring Data JPA simplifies the implementation of JPA-based repositories by integrating seamlessly into the Spring ecosystem. With this powerful tool, you can efficiently perform database operatio
6 min read
Spring Data JPA vs Spring JDBC Template
In this article, we will learn about the difference between Spring Data JPA vs Spring JDBC Template. Spring Data JPATo implement JPA-based repositories, Spring Data JPA, a piece of the Spring Data family, takes out the complexity. With the help of spring data JPA the process of creating Spring-power
5 min read
findBy Methods in Spring Data JPA Repositories
Spring Data JPA abstracts the boilerplate code required to interact with the database, allowing developers to focus more on business logic rather than database connectivity and query formation. The findBy() method, in particular, is a part of the repository layer in Spring Data JPA, enabling develop
5 min read
Spring Bean Validation - JSR-303 Annotations
In this article, we'll explore practical examples of how to apply JSR-303 annotations to your domain objects from basic annotations to advanced. So basically annotations provide a declarative way to configure Spring beans, manage dependencies, and define behaviors, reducing the need for boilerplate
6 min read
Spring Boot - Spring Data JPA
Spring Data JPA or JPA stands for Java Persistence API, so before looking into that, we must know about ORM (Object Relation Mapping). So Object relation mapping is simply the process of persisting any java object directly into a database table. Usually, the name of the object being persisted become
6 min read