Mục lục
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_READ – cho 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_INCREMENT – giố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ộ.
- LockTimeoutException – 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 bị time-out, kết quả là các statement-level sẽ bị roll-back toàn bộ.
- PersistanceException – Chỉ ra một vấn đề khi làm việc với dữ liệu, nó là một subclass của NonUniqueResultException, LockTimeoutException và QueryTimeoutException, 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