Tìm hiểu Pessimistic Locking trong JPA

Có rất nhiều vấn đề có thể xảy ra khi chúng ta làm việc với dữ liệu từ database. Chẳng hạn như 2 transaction cùng truy xuất đến một dòng dữ liệu trong database, hay cả 2 transaction đang cố gắng cập nhật cùng một dòng dữ liệu cùng lúc với nhau etc.

Đối mặt với những trường hợp trên, đôi lúc chúng ta muốn chiếm giữ dữ liệu mà transaction hiện tại đang nắm giữ không cho phép bất kỳ một transaction tác động đến những dữ liệu này.

Thật may rằng JPA có cung cấp một cơ chế được nêu như trên gọi là pessimistic lock cung cấp cơ chế khoá dữ liệu, đảm bảo không có transaction nào khác có thể sửa đổi hoặc xoá dữ liệu mà transaction hiện tại đang chiếm giữ, nó còn được gọi là exclusive lock.

Khi một entity bị khoá với pessimistic lock, nó sẽ có 2 loại khoá như sau:

  • shared lock – Chỉ có thể đọc nhưng không thể xoá, cập nhật dữ liệu.
  • exclusive lock – Có thể xoá hoặc cập nhật dữ liệu .

Lock Modes

JPA định nghĩa 3 kiểu pessimistic lock mà chúng ta sẽ tìm hiểu ngay sau đây

  • PESSIMISTIC_READcho chúng ta một chìa khoá gọi là shared lock, với khoá này chúng ta chỉ có thể đọc dữ liệu.
  • PESSIMISTIC_WRITE nhận một chìa khoá gọi là exclusive lock, chúng ta sẽ có toàn quyền đọc, xoá, chỉnh sửa dữ liệu và ngăn không cho các transaction khác đọc, xoá, và cập nhật dữ liệu lên chúng.
  • PESSIMISTIC_FORCE_INCREMENTgiống với cơ chế hoạt động của PESSIMISTIC_WRITE, ngoài ra nó còn tăng giá trị của thuộc tính version trong entity

PESSIMISTIC_READ

Bất cứ khi nào bạn chỉ muốn đọc dữ liệu mà không bị làm phiền bởi các transaction khác, thì chỉ cần sử dụng PESSIMISTIC_READ lock (shared lock). Chúng ta sẽ không thể xoá hoặc sửa đổi dữ liệu khi sử dụng loại khoá PESSIMISTIC_READ này.

PESSIMISTIC_WRITE

Bất kỳ một transaction nào muốn chiếm giữ dữ liệu và thực hiện các thay đổi trên chúng thì nó nên nhận một khoá PESSIMISTIC_WRITE. Nhưng hãy lưu ý, JPA chỉ ra rằng PESSIMISTIC_WRITE sẽ ngăn chặn tất cả các transaction khác xoá, sửa đổi hoặc thậm chí không được phép đọc dữ liệu.

Nhưng có một số hệ thống database triển khai multi-version concurrency control có thể cho phép các transaction khác đọc dữ liệu ngay cả khi chúng đang bị khoá.

PESSIMISTIC_FORCE_INCREMENT

Loại khoá này hoạt động tương tự PESSIMISTIC_WRITE lock, nhưng mục đích sử dụng chính của nó là kết hợp với thuộc tính version – được đánh dấu với @Version annotation. Ngoài ra nó còn tăng giá trị của thuộc tính version trong entity

Một entity có thuộc tính version thì nên sử dụng PESSIMISTIC_FORCE_INCREMENT thay vì PESSIMISTIC_WRITE.

Exceptions

Trong quá trình làm việc với pessimistic lock chúng ta cần biết một số exception và ý nghĩa của nó để có hướng giải quyết cụ thể:

  • PessimisticLockException – Chỉ ra rằng quá trình nhận khoá (shared lock hoặc exclusive lock) hoặc chuyển đổi từ shared lock sang exclusive lock xảy ra lỗi, kết quả là transaction sẽ bị roll-back toàn bộ.
  • LockTimeoutExceptionChỉ ra rằng quá trình nhận khoá (shared lock hoặc exclusive lock) hoặc chuyển đổi từ shared lock sang exclusive lock bị time-out, kết quả là các statement-level sẽ bị roll-back toàn bộ.
  • PersistanceExceptionChỉ ra một vấn đề khi làm việc với dữ liệu, nó là một subclass của NonUniqueResultException, LockTimeoutExceptionQueryTimeoutException, khiến cho transaction hiện tại bị roll-back.

Sử dụng Pessimistic Lock

Để sử dụng Pessimistic lock trong JPA chúng ta có một số cách sử dụng sau:

Find

Đây có lẽ là trường hợp có thể dễ dàng thấy được chỉ nên sử dụng PESSIMISTIC_READ vì nhu cầu chỉ có đọc dữ liệu.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
 
    @Id
    private Long id;
    private String name;
    private String lastName;
 
    // getters and setters
}
 
@Entity
public class Employee extends Person {
 
    private BigDecimal salary;
 
    // getters and setters
}
entityManager.find(Student.class, studentId, LockModeType.PESSIMISTIC_READ)

Query

Ngoài ra chúng ta có thể sử dụng Query object với setLockMode method nhận vào LockModeType.

Query query = entityManager.createQuery("from Student where studentId = :studentId");
query.setParameter("studentId", studentId);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.getResultList()

Explicit Locking

Bạn cũng có thể chỉ định một cách thủ công với những dữ liệu mà bạn mong muốn xoá hay sửa đổi chúng.

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.lock(resultStudent, LockModeType.PESSIMISTIC_WRITE);

Refresh

Nếu muốn cập nhật trạng thái mới nhất của một entity thì bạn có thể sử dụng refesh method

Student resultStudent = entityManager.find(Student.class, studentId);
entityManager.refresh(resultStudent, LockModeType.PESSIMISTIC_FORCE_INCREMENT);

NamedQuery

Ngoài ra, JPA còn cho phép chúng ta chỉ định thông qua lockMode khi sử dụng @NamedQuery

@NamedQuery(name="lockStudent",
  query="SELECT s FROM Student s WHERE s.id LIKE :studentId",
  lockMode = PESSIMISTIC_READ)

Lock Scope

Lock Scope cho phép chúng ta xử lý các related entity(các entity có quan hệ với locked entity) với entity đang bị khoá (locked entity).

Để cấu hình Lock Scope chúng ta cần sử dụng PessimisticLockScope chứa 2 giá trị NORMAL và EXTENDED.

Map<String, Object> properties = new HashMap<>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);
    
entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC_WRITE, properties);

PessimisticLockScope.NORMAL

Đây là Lock Scope mặc định trong JPA, trong đó khi sử dụng chỉ duy nhất một entity bị khoá lại.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Person {
 
    @Id
    private Long id;
    private String name;
    private String lastName;
 
    // getters and setters
}
 
@Entity
public class Employee extends Person {
 
    private BigDecimal salary;
 
    // getters and setters
}

Khi chúng ta muốn khoá một  Employee entity ( khoá Employee entity lại) thì chúng ta có thể thấy SQL sinh ra 

SELECT t0.ID, t0.DTYPE, t0.LASTNAME, t0.NAME, t1.ID, t1.SALARY 
FROM PERSON t0, EMPLOYEE t1 
WHERE ((t0.ID = ?) AND ((t1.ID = t0.ID) AND (t0.DTYPE = ?))) FOR UPDATE

Câu lệnh SELECT … FOR UPDATE chỉ ra rằng entity đó đang bị khoá lại.

PessimisticLockScope.EXTENDED

Ngoài tính năng như PessimisticLockScope.NORMAL thì

PessimisticLockScope.EXTENDED còn khoá cả các related entity

@Entity
public class Customer {
 
    @Id
    private Long customerId;
    private String name;
    private String lastName;
    @ElementCollection
    @CollectionTable(name = "customer_address")
    private List<Address> addressList;
 
    // getters and setters
}
 
@Embeddable
public class Address {
 
    private String country;
    private String city;
 
    // getters and setters
}

Hãy xem đoạn SQL sau khi mình thực hiên truy vấn Customer entity.

SELECT CUSTOMERID, LASTNAME, NAME 
FROM CUSTOMER WHERE (CUSTOMERID = ?) FOR UPDATE
 
SELECT CITY, COUNTRY, Customer_CUSTOMERID 
FROM customer_address 
WHERE (Customer_CUSTOMERID = ?) FOR UPDATE

Setting Lock Timeout

Trong lock scope, chúng ta còn có thêm một tham số nữa là timeout có giá trị milliseconds, đại diện cho một giới hạn thời gian mà chúng ta muốn đợi để nhậ được một khoá.

Map<String, Object> properties = new HashMap<>(); 
map.put("javax.persistence.lock.timeout", 1000L); 
 
entityManager.find(
  Student.class, 1L, LockModeType.PESSIMISTIC_READ, properties);

Tóm lược

Như vậy là chúng ta đã tìm hiểu xong một cơ chế khoá dữ liệu đầu tiên trong JPA, giúp chúng ta giải quyết các vấn đề đa luồng trong xử lý dữ liệu. Kỳ tới chúng ta sẽ tìm hiểu thêm một cơ chế nữa gọi là Optimistic locking.

Nguồn tham khảo

https://www.baeldung.com/jpa-pessimistic-locking#2-pessimisticlockscopeextended

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x