Học viên: Nguyễn Chí Hiếu
Lớp: Java Fullstack 15
Email: hieunc82@gmail.com
Số điện thoại: 0989555658
Nguồn tham khảo: https://www.baeldung.com/spring-data-jpa-modifying-annotation


1. Giới thiệu

Trong hướng dẫn ngắn này, chúng ta sẽ tìm hiểu cách tạo các truy vấn cập nhật với @Query Annotation của Spring Data JPA. Chúng ta sẽ đạt được điều này bằng cách sử dụng @Modifying Annotation.

Trước tiên, để chúng ta thuận lợi khi tìm hiểu, chúng ta có thể đọc cách thực hiện truy vấn bằng Spring Data JPA. Sau đó, chúng ta sẽ đi sâu vào việc sử dụng các Annotation @Query và @Modifying. Cuối cùng, chúng ta sẽ thảo luận về cách quản lý trạng thái của ngữ cảnh bền vững của chúng ta khi sử dụng nhiều truy vấn Modifying.

2. Truy vấn trong Spring Data JPA

Trước tiên, hãy tóm tắt ba cơ chế mà Spring Data JPA cung cấp để truy vấn dữ liệu trong cơ sở dữ liệu:

  • Query methods
  • @Query Annotation
  • Custom repository implementation

Hãy tạo một lớp User và một repository Spring Data JPA tương ứng để minh họa các cơ chế này:

@Entity
@Table(name = "users", schema = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private LocalDate creationDate;
    private LocalDate lastLoginDate;
    private boolean active;
    private String email;

}
public interface UserRepository extends JpaRepository<User, Integer> {}

Cơ chế các phương thức truy vấn cho phép chúng ta thao tác dữ liệu bằng cách suy ra các truy vấn từ tên phương thức:

List<User> findAllByName(String name);
void deleteAllByCreationDateAfter(LocalDate date);

Trong ví dụ này, chúng ta có thể tìm thấy một truy vấn để lấy người dùng theo tên của họ, hoặc một truy vấn để xóa người dùng có ngày tạo sau một ngày nhất định.

Về @Query Annotation này, nó cung cấp cho chúng ta cơ hội để viết một truy vấn JPQL hoặc SQL cụ thể trong @Query Annotation:

@Query("select u from User u where u.email like '%@gmail.com'")
List<User> findUsersWithGmailAddress();

Trong đoạn mã này, chúng ta có thể thấy một truy vấn lấy các người dùng có địa chỉ email @gmail.com.

Cơ chế đầu tiên cho phép chúng ta truy xuất hoặc xóa dữ liệu. Về cơ chế thứ hai, nó cho phép chúng ta thực hiện gần như bất kỳ truy vấn nào. Tuy nhiên, đối với các truy vấn cập nhật, chúng ta phải thêm @Modifying Annotation. Điều này là chủ đề của bài viết này.

3. Sử dụng @Modifying Annotation

@Modifying Annotation được sử dụng để tăng cường @Query Annotation để chúng ta có thể thực hiện không chỉ các truy vấn SELECT, mà còn các truy vấn INSERT, UPDATE, DELETE, và thậm chí là các truy vấn DDL.

Bây giờ hãy thử nghiệm với Annotation này một chút.

Trước tiên, hãy xem một ví dụ về truy vấn @Modifying UPDATE:

@Modifying
@Query("update User u set u.active = false where u.lastLoginDate < :date")
void deactivateUsersNotLoggedInSince(@Param("date") LocalDate date);

Ở đây, chúng ta đang chuyển thuộc tính “active” của các “User” chưa đăng nhập kể từ một ngày nhất định sang “false”.
Hãy thử một ví dụ khác, trong đó chúng ta sẽ xóa các “User” có “active” = false:

@Modifying
@Query("delete User u where u.active = false")
int deleteDeactivatedUsers();

Như chúng ta có thể thấy, phương thức này trả về một số nguyên. Đây là một tính năng của truy vấn @Modifying của Spring Data JPA cung cấp cho chúng ta số lượng các thực thể (Entities) được cập nhật.

Chúng ta nên lưu ý rằng việc thực thi một truy vấn xóa với @Query hoạt động khác biệt so với các phương pháp xóa có tên được tạo ra bằng cách dựa trên tên trong Spring Data JPA. Các phương pháp này trước tiên truy xuất các thực thể từ cơ sở dữ liệu, sau đó xóa chúng một cách tuần tự. Điều này có nghĩa là life-cycle của phương thức @PreRemove sẽ được gọi trên những thực thể đó. Tuy nhiên, với truy vấn phía trên, một truy vấn đơn được thực thi trên cơ sở dữ liệu.

Cuối cùng, hãy thêm một cột deleted vào bảng USERS của chúng ta với một truy vấn DDL:

@Modifying
@Query(value = "alter table USERS.USERS add column deleted int(1) not null default 0", nativeQuery = true)
void addDeletedColumn();

Thật không may, việc sử dụng nhiều truy vấn Modifying sẽ khiến ngữ cảnh bền vững của cơ sở dữ liệu bị lỗi thời. Tuy nhiên, có thể quản lý được tình huống này. Đó là chủ đề của phần tiếp theo.

3.1. Kết quả khi không sử dụng @Modifying Annotation

Hãy xem điều gì sẽ xảy ra khi chúng ta không đặt @Modifying Annotation trên truy vấn xóa.

Vì lí do này, chúng ta cần tạo một phương thức khác:

@Query("delete User u where u.active = false")
int deleteDeactivatedUsersWithNoModifyingAnnotation();

Chú ý truy vấn trên (Modifying) Annotation thiếu.

Khi chúng ta thực thi phương thức trên, chúng ta sẽ nhận được một ngoại lệ InvalidDataAccessApiUsage:

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.QueryExecutionRequestException: 
Not supported for DML operations [delete com.baeldung.boot.domain.User u where u.active = false]
(...)

Thông báo lỗi rất rõ ràng: truy vấn không được hỗ trợ cho các thao tác DML.

4. Quản lý ngữ cảnh bền vững (Persistence Context)

Nếu truy vấn cập nhật của chúng ta thay đổi các đối tượng chứa trong ngữ cảnh bền vững, thì ngữ cảnh này sẽ trở nên lỗi thời. Một cách để quản lý tình huống này là xóa ngữ cảnh bền vững. Bằng cách làm điều đó, chúng ta đảm bảo rằng ngữ cảnh bền vững sẽ lấy các đối tượng từ cơ sở dữ liệu lần tiếp theo.

Tuy nhiên, chúng ta không cần gọi phương thức clear() một cách rõ ràng trên EntityManager. Chúng ta chỉ cần sử dụng thuộc tính clearAutomatically từ @Modifying Annotation:

@Modifying(clearAutomatically = true)

Theo cách này, chúng ta đảm bảo rằng ngữ cảnh bền vững sẽ được xóa sau khi thực thi truy vấn của chúng ta.
Tuy nhiên, nếu ngữ cảnh bền vững chứa các thay đổi chưa được lưu, việc xóa nó sẽ có nghĩa là loại bỏ các thay đổi chưa được lưu đó. May mắn thay, có một thuộc tính khác của chú thích mà chúng ta có thể sử dụng trong trường hợp này, đó là flushAutomatically:

@Modifying(flushAutomatically = true)

Bây giờ, EntityManager được thay đổi trước khi truy vấn của chúng ta được thực thi.

5. Tổng kết

Đó là những gì chúng ta cần biết về @Modifying Annotation. Chúng ta đã học cách sử dụng Annotation này để thực thi các truy vấn cập nhật, như INSERT, UPDATE, DELETE và thậm chí là DDL. Sau đó, chúng ta đã thảo luận về cách quản lý trạng thái của ngữ cảnh bền vững với các thuộc tính clearAutomatically và flushAutomatically.

Như thường lệ, mã đầy đủ cho bài viết này có sẵn trên GitHub.