+1

Pessimistic Locking in JPA

1.Tổng quan

Trong cơ sở dữ liệu quan hệ (Relation Database), locking là chỉ các hành động tránh việc dữ liệu bị thay đổi giữa thời gian đọc và thời gian sử dụng

Vậy khi nào thì cần phải sử dụng Locking

Ví dụ :

Bạn có một tài khoản ngân hàng, giả sử tài khoản của bạn có 1 triệu

Bây giờ bạn vừa nhờ 1 người thực hiện rút tiền 1 triệu đồng ở cây ATM còn mình thì đồng thời thực hiện chuyển khoản trên Internet Bank với giá trị 1 triệu.

Cả hai hành động trên đều diễn ra đồng thời => bạn có 1 triệu đồng mà rút được 1 triệu lại còn chuyển khoản được 1 triệu => lời 1 triệu rồi, đơn giản quá

Đùa thôi 😃 ,tất nhiên đây lúc locking giải quyết tình huống này.

Có 2 loại locking trong Hibernate/JPA là Optimistic lock và Pessimstic lock.

Trong bài viết này sẽ giới thiệu về Pessimstic lock(JPA)

2.Pessimstic lock hoạt động như thế nào

Locking là một cơ chế hoạt đọng bằng cách lock một row trước khi thuộc tính của nó thay đổi thông qua việc gọi đến method setAtribute. Nếu có bất kì transaction khác cố gắng truy cập vào row đã bị khóa đó, chúng sẽ bị buộc phải chờ cho đến khi transaction đầu tiên hoàn thành

Cơ chế này sử dụng syntax SELECT...FOR UPDATE. Pessimistic locking là cơ chế locking an toàn nhất bởi việc hai transactions sẽ không bao giờ có thể cùng thay đổi một row

3. Lock Mode

JPA có ba loại pessimstic lock :

PESSIMISTIC_READ - Cho phép bạn có một shared lock và ngăn chặn việc updated và deleted

PESSIMISTIC_WRITE - Cho phép bạn có một exclusive lock ngăn chăn việc read , updated , deleted

PESSIMISTIC_FORCE_INCREMENT - Giống với PESSIMISTIC_WRITE và bổ xung thêm thuộc tính version của version entity

Tất cả chúng đều là static members của lớp LockModeType và cho phép các transaction có được database lock. Tất cả đều được giữ lại cho đến khi transaction commits hoặc rolls back.

3 . Sử dụng Pessimistic Locks

Có vài cách để cấu hình một Pessimistic Locks trên một record hoặc một group records.Trong bài viết này chúng ta sẽ dùng JPA để làm việc này.

3.1.FIND

Đây là một cách đơn giản .

entityManager.find(Student.class, studentId, LockModeType.PESSIMISTIC_READ);

3.2 Query

Chúng ta có thể sử dụng Query object và call setLockMode setter với một lock mode là một parameter:

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

3.3 Explicit Locking

Có thể khóa thủ công các kết quả được truy xuất bằng phương thức find:

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

3.4 Refresh

Bạn muốn overwrite trạng thái cảu của entity bằng phương thức refresh :

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

3.5 NamedQuery

@NamedQuery annotation cũng là một cách cho phép chúng ta set một lock : @NamedQuery(name="lockStudent", query="SELECT s FROM Student s WHERE s.id LIKE :studentId", lockMode = PESSIMISTIC_READ)

4.Lock Scope

Lock scope cho phép chúng ta locking relationships của entity đang locking. Nó có thể có được một lock chỉ trên một thực thể được xác định trong truy vấn hoặc chặn thêm các mối relationships của nó.

Để cấu hình scope chúng ta sử dụng PessimisticLockScope enum . Nó chứa hai giá trị NORMAL và EXTENDED.

Chúng ta có thể set scope qua một parameter 'javax.persistance.lock.scope' với giá trị là một đối số cho phương thức thích hợp của EntityManager, Query, TypedQuery hoặc NamedQuery:

Map<String, Object> properties = new HashMap<>(); map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);

entityManager.find( Student.class, 1L, LockModeType.PESSIMISTIC_WRITE, properties);

4.1 PessimisticLockScope.NORMAL

Chúng ta nên biết rằng PessimisticLockScope.NORMAL là default scope . Với locking scope này , chúng lock entity, khi sử dụng kế thừa nó cũng nó cũng lock luôn.

@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
}

Bạn cũng có thể sử dụng lock trên Employee qua truy vấn :

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

4.2. PessimisticLockScope.EXTENDED

EXTENDED có chúc nằng giống với NORMAL. Thêm nữa nó có khả năng block related entities trong join table.

Nó làm việc các anotated @ElementCollection or @OneToOne, @OneToMany etc. với @JoinTable.

@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
}

Hay phân tích một chút về hai câu queries này :

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

Chúng ta có thể thấy có hai queries ‘FOR UPDATE‘ sẽ khó một hàng trong bảng customer cũng như một hàng trong bảng được join

Không phải tất cả các nhà persistence providers support lock scopes.

5.Setting Lock Timeout

Bên cạnh việc thiết lập lock scopes, chúng ta có thể điều chỉnh một tham số khóa khác - timeout. Giá trị timeout là số mili giây mà chúng ta muốn đợi để có được lock cho đến khi LockTimeoutException xảy ra.

Chúng ta có thể thay đổi giá trị của timeout tương tự như lock scopes, bằng cách sử dụng thuộc tính ‘javax.persistence.lock.timeout, với số mili giây thích hợp

Map<String, Object> properties = new HashMap<>(); map.put("javax.persistence.lock.timeout", 1000L);

entityManager.find( Student.class, 1L, LockModeType.PESSIMISTIC_READ, properties);

6. Kết luận

Bạn có thể tham khảo bài viết gốc :https://www.baeldung.com/jpa-pessimistic-locking


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí