Skip to main content

Command Palette

Search for a command to run...

Bài 6: Two-Phase Locking Protocol trong Spring

Published
6 min readView as Markdown

Giới thiệu về Two-Phase Locking (2PL)

Two-Phase Locking (2PL) là một giao thức quản lý khóa trong cơ sở dữ liệu nhằm đảm bảo rằng các giao dịch được thực hiện một cách tuần tự và tránh các tình trạng xung đột dữ liệu. 2PL hoạt động dựa trên hai giai đoạn chính:

  1. Growing Phase (Giai đoạn tăng trưởng): Trong giai đoạn này, transaction chỉ được phép lấy thêm khóa và không được phép giải phóng bất kỳ khóa nào.

  2. Shrinking Phase (Giai đoạn co rút): Trong giai đoạn này, transaction chỉ được phép giải phóng khóa và không được phép lấy thêm bất kỳ khóa nào.

Nguyên tắc hoạt động và tầm quan trọng

Giao thức Two-Phase Locking đảm bảo rằng tất cả các thao tác khóa đều được hoàn thành trước khi bắt đầu giai đoạn giải phóng khóa. Điều này giúp tránh được các vấn đề xung đột và đảm bảo tính tuần tự của các giao dịch, từ đó duy trì tính toàn vẹn và nhất quán của dữ liệu.

Ví dụ, trong một hệ thống e-commerce, nhiều người dùng có thể đồng thời thêm sản phẩm vào giỏ hàng hoặc tiến hành thanh toán. Sử dụng 2PL đảm bảo rằng các thao tác này được thực hiện một cách tuần tự, tránh xung đột và lỗi dữ liệu.

Phân biệt giữa Growing Phase và Shrinking Phase

  • Growing Phase: Giai đoạn mà transaction có thể lấy thêm khóa để đảm bảo rằng các thao tác của nó sẽ không bị xung đột với các transaction khác. Transaction không được phép giải phóng khóa trong giai đoạn này.

  • Shrinking Phase: Giai đoạn mà transaction bắt đầu giải phóng các khóa mà nó đã giữ. Trong giai đoạn này, transaction không được phép lấy thêm bất kỳ khóa nào mới.

Áp dụng Two-Phase Locking trong Spring

Sử dụng các cơ chế khóa trong Hibernate

Hibernate, một framework ORM phổ biến, hỗ trợ nhiều cơ chế khóa khác nhau để đảm bảo tính tuần tự và nhất quán của dữ liệu. Các cơ chế khóa này có thể được sử dụng để triển khai Two-Phase Locking trong các ứng dụng Spring.

Pessimistic Locking với Hibernate

Pessimistic Locking trong Hibernate sử dụng các cơ chế khóa ở cấp độ cơ sở dữ liệu để ngăn chặn các transaction khác truy cập vào dữ liệu mà một transaction đang thao tác. Điều này đảm bảo rằng dữ liệu không bị xung đột trong quá trình thực hiện transaction.

Ví dụ về Pessimistic Locking với Hibernate:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String status;

    @Version
    private Integer version;

    // Getters và Setters
}

Trong repository:

import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

import javax.persistence.LockModeType;
import java.util.Optional;

public interface OrderRepository extends CrudRepository<Order, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT o FROM Order o WHERE o.id = :id")
    Optional<Order> findByIdWithLock(Long id);
}

Trong service:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    public void updateOrderStatus(Long orderId, String newStatus) {
        Order order = orderRepository.findByIdWithLock(orderId).orElseThrow(() -> new RuntimeException("Order not found"));
        order.setStatus(newStatus);
        orderRepository.save(order);
    }
}
Optimistic Locking với Hibernate

Optimistic Locking sử dụng phiên bản (version) để kiểm tra tính nhất quán của dữ liệu. Khi một transaction cố gắng ghi dữ liệu, Hibernate sẽ kiểm tra phiên bản hiện tại của dữ liệu. Nếu phiên bản không khớp, transaction sẽ bị rollback.

Ví dụ về Optimistic Locking với Hibernate:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private Integer quantity;

    @Version
    private Integer version;

    // Getters và Setters
}

Trong repository:

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

Trong service:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public void updateProduct(Product product) {
        productRepository.save(product);
    }
}
Cấu hình để đảm bảo tính tuần tự

Để đảm bảo tính tuần tự trong các transaction, bạn có thể cấu hình isolation level và sử dụng các cơ chế khóa phù hợp trong Spring. Ví dụ, bạn có thể sử dụng @Transactional với isolation = Isolation.SERIALIZABLE để đảm bảo rằng các transaction được thực hiện một cách tuần tự và không bị xung đột.

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class InventoryService {

    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void manageInventory(Long productId, int quantity) {
        // Thực hiện các thao tác quản lý tồn kho
    }
}

Ví dụ minh họa

Thực hiện giao dịch với nhiều bước cập nhật dữ liệu

Giả sử bạn có một hệ thống e-commerce cần thực hiện các giao dịch phức tạp với nhiều bước cập nhật dữ liệu, như xử lý đơn hàng, cập nhật tồn kho và ghi nhận thanh toán. Bạn có thể sử dụng Two-Phase Locking để đảm bảo rằng các bước này được thực hiện một cách tuần tự và không bị xung đột.

  1. Tạo entity Order và Product:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String status;

    @Version
    private Integer version;

    // Getters và Setters
}

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private Integer quantity;

    @Version
    private Integer version;

    // Getters và Setters
}
  1. Tạo repository cho Order và Product:
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

import javax.persistence.LockModeType;
import java.util.Optional;

public interface OrderRepository extends CrudRepository<Order, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT o FROM Order o WHERE o.id = :id")
    Optional<Order> findByIdWithLock(Long id);
}

public interface ProductRepository extends CrudRepository<Product, Long> {
}
  1. Tạo service để quản lý transaction:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class EcommerceService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public void processOrder(Long orderId, Long productId, int quantity) {
        // Khóa đơn hàng để cập nhật trạng thái
        Order order = orderRepository.findByIdWithLock(orderId).orElseThrow(() -> new RuntimeException("Order not found"));
        order.setStatus("PROCESSING");
        orderRepository.save(order);

        // Khóa sản phẩm để cập nhật số lượng
        Product product = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("Product not found"));
        if (product.getQuantity() < quantity) {
            throw new RuntimeException("Not enough stock");
        }
        product.setQuantity(product.getQuantity() - quantity);
        productRepository.save(product);

        // Cập nhật trạng thái đơn hàng thành công
        order.setStatus("COMPLETED");
        orderRepository.save(order);
    }
}
  1. Sử dụng service trong controller:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EcommerceController {

    @Autowired
    private EcommerceService ecommerceService;

    @PostMapping("/process-order")
    public String processOrder(@RequestParam Long orderId, @RequestParam Long productId, @RequestParam int quantity) {
        ecommerceService.processOrder(orderId, productId, quantity);
        return "Order processed successfully";
    }
}

Câu hỏi củng cố kiến thức

  1. **Two-Phase Locking (2PL) là gì và tại sao

nó quan trọng trong quản lý transaction?**

  • 2PL là giao thức quản lý khóa đảm bảo các giao dịch được thực hiện tuần tự để tránh xung đột. Nó quan trọng vì giúp duy trì tính toàn vẹn và nhất quán của dữ liệu.
  1. Growing Phase và Shrinking Phase trong 2PL khác nhau như thế nào?

    • Growing Phase là giai đoạn transaction lấy thêm khóa và không giải phóng khóa, còn Shrinking Phase là giai đoạn transaction giải phóng khóa và không lấy thêm khóa.
  2. Làm thế nào để sử dụng Pessimistic Locking và Optimistic Locking trong Hibernate?

    • Pessimistic Locking sử dụng LockModeType.PESSIMISTIC_WRITE để khóa dữ liệu khi đọc, còn Optimistic Locking sử dụng phiên bản (@Version) để kiểm tra tính nhất quán khi ghi dữ liệu.

More from this blog

hoangkim

366 posts