Tổng hợp tất cả các Casecade JPA-Hibernate hỗ trợ

JPA-Hibernate giúp chúng ta đồng bộ hoá các quá trình chuyển đổi trạng thái của entity xuống database. Quá trình thay đổi trạng thái của các entity trong JPA-Hibernate có khả năng gây rảnh hưởng đến các entity có liên kết với chúng. Để giải quyết vấn đề này, Hibernate đã giới thiệu Cascase mà chúng ta sẽ tìm hiểu ngay sau đây.

Cascade là gì?

Ví dụ UserOrder có quan hệ one-to-many, khi User bị xoá khỏi database việc các Order tồn tại riêng lẽ là không có ý nghĩa vì vậy chúng cần được xoá theo User.Nếu trước đây thì có lẽ chúng ta phải xoá chúng một cách thủ công, nhưng Cascade được Hibernate giới thiệu để giải quyết vấn đề này một cách tự động.

Như đã bàn luận ở trên thì Cascade là cơ chế cho phép lan truyền quá trình chuyển đổi từ một entity đến các related entity(các entity có mối quan hệ với một entity khác).

JPA Cascade Type

JPA hỗ trợ các kiểu tất cả gồm 6 kiểu được định nghĩa trong javax.persistence.CascadeType enum:

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

Hibernate Cascade Type

Ngoài các kiểu mà JPA hỗ trợ thì Hibernate còn hỗ trợ thêm 3 kiểu được định nghĩa trong org.hibernate.annotations.CascadeType enum:

  • REPLICATE
  • SAVE_UPDATE
  • LOCK

Maven dependency

Để thực hành được bài này chúng ta cần sử những dependency sau trong project maven.

Nếu bạn chưa biết cách cấu hình JPA-Hibernate thì có thể xem qua bài hướng dẫn này hoặc có thể theo dõi để nắm ý chính rồi thực hành sau cũng được.

<dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.0.0.Final</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-java8</artifactId>
        <version>5.1.0.Final</version>
    </dependency>
</dependencies>

Giả sử mình có mô hình 2 bảng dữ liệu gồm User, Order được ánh xạ như sau:

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    private String name;
    @OneToMany(mappedBy = "user")
    private Set<Order> orders = new HashSet<>();
    // getter, setter, construtor
}
@Entity
@Table(name = "orders")
public class Order {

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

    private LocalDate date;

    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
    // // getter, setter, construtor
}

Điểm khác nhau của các loại Cascade

Mỗi loại Cascade trong JPA và Hibernate đều có mục đích sử dụng riêng, mà trong phần này chúng ta cùng tìm hiểu từng loại một.

Cascade.PERSIST

Như bạn đã biết hàm JPA#Persist() chuyển một entity từ trạng thái transient(transient entity) sang persistent(persistent entity). Cascade.PERSIST sẽ lan truyền hoạt động này một cách tự động từ persistent entity sang các related entity. Ví dụ khi chúng ta lưu một User entity thì các Order entity liên kết với nó cũng được lưu theo.

Để kiểm tra, trước tiên mình thêm vào ánh xạ của User với CascadeType.PERSIST

@Entity
@Table(name = "user")
public class User {

    @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
    private Set<Order> orders = new HashSet<>();
    // .... 
}
User user = new User();
Order order = new Order();
order.setUser(user);
user.getOrders().add(order);
em.persist(user);

Với Cascade.PERSIST chỉ cần chúng ta lưu User thì Order sẽ tự động lưu theo.

Hibernate: 
    /* insert entities.User
        */ insert 
        into
            user
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert entities.Order
        */ insert 
        into
            orders
            (date, user_id, id) 
        values
            (?, ?, ?)

CascadeType.MERGE

Merge trong JPA-Hibernate giúp hợp nhất detached entity với persistent entity có cùng mã định danh. Cascade.Merge sẽ lan truyền hoạt động này sang các related entity.

@Entity
@Table(name = "user")
public class User {

    @OneToMany(mappedBy = "user", cascade = CascadeType.MERGE)
    private Set<Order> orders = new HashSet<>();
    // .... 
}
Long userId;
User user = new User();
Order order = new Order();
user.getOrders().add(order);
order.setUser(user);

// Lưu xuống database
em.persist(user);
em.persist(order);
em.flush();

// Cập nhật ID cho User và Order
em.refresh(user);
userId = user.getId();
// Clear tất cả các persisent entity
em.clear();

User userSaved = em.find(User.class, userId);
Order orderSaved = userSaved.getOrders().iterator().next();
// merge entity và cascade
userSaved.setName("Name updated");
orderSaved.setDate(LocalDate.now());

em.merge(userSaved);

Đoạn code trên mô tả quá trình tạo và lưu trữ 2 UserOrder entity xuống database, sau khi làm mới session (session#clear) thì chúng sẽ rơi vào trạng thái detached. Sau đó mình đã lấy chúng lên và cập nhật dữ liệu, lúc này trong database đã tồn tại 2 dòng dữ liệu tương ứng với User và Order. Sử dụng em.merge(user) sẽ giúp chúng ta cập nhật lại dữ liệu của User và cả Order vì chúng được ánh xạ với CasCase.MERGE.

Hibernate: 
    /* update
        entities.User */ update
            user 
        set
            name=? 
        where
            id=?
Hibernate: 
    /* update
        entities.Order */ update
            orders 
        set
            date=?,
            user_id=? 
        where
            id=?

CasCade.REMOVE

Nhìn vào cái tên có lẽ chúng ta cũng đoán được phần nào, khi một entity bị xóa khởi database thì các related entity cũng bị xóa theo.

@Entity
@Table(name = "user")
public class User {

    @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
    private Set<Order> orders = new HashSet<>();
    // .... 
}

Để lấy ví dụ minh họa trước tiên chúng ta cần thêm dữ liệu vào database.

INSERT INTO `cascade`.`user` (`id`, `name`) VALUES ('1', 'name');

INSERT INTO `cascade`.`orders` (`id`, `date`, `user_id`) VALUES( 1, current_date(), 1);
User user = em.find(User.class, 1L);
em.remove(user);

Sau khi thực thi đoạn code trên thì chúng ta có thể thấy rằng Hibernate đã sinh ra đến 2 câu DELETE, trước tiên nó xóa các Order liên quan đến User và sau đó tiến hành xóa luôn User.

Hibernate: 
    /* delete entities.Order */ delete 
        from
            orders 
        where
            id=?
Hibernate: 
    /* delete entities.User */ delete 
        from
            user 
        where
            id=?

CasCade.DETACHED

EntityManager#detach dùng để xóa một entity ra khỏi persistenct context. Khi sử dụng Cascade.REMOVE, các related entity với nó cũng sẽ bị loại bỏ khởi persistence context.

@Entity
@Table(name = "user")
public class User {

    @OneToMany(mappedBy = "user", cascade = CascadeType.DETACH)
    private Set<Order> orders = new HashSet<>();
    // .... 
}

Hãy đảm bảo database của bạn có dữ liệu để có thể hoàn thành được ví dụ

INSERT INTO `cascade`.`user` (`id`, `name`) VALUES ('1', 'name');

INSERT INTO `cascade`.`orders` (`id`, `date`, `user_id`) VALUES( 1, current_date(), 1);

Sử dụng EntityManager#constains method để kiểm tra một entity có tồn tại trong Persistence context hay không

User user = em.find(User.class, 1L);
Order order = user.getOrders().iterator().next();

System.out.println("Before deteach");
System.out.println("User belong to Persistence context: " + em.contains(user));
System.out.println("User belong to Persistence context: " + em.contains(order));

em.detach(user);
System.out.println("After deteach");

System.out.println("User belong to Persistence context: " + em.contains(user));
System.out.println("User belong to Persistence context: " + em.contains(order));

Output

Before deteach
User belong to Persistence context: true
User belong to Persistence context: true
After deteach
User belong to Persistence context: false
User belong to Persistence context: false

CascadeType.REFRESH

Trong một số trường hợp bạn đã thay đổi các thuộc tính của một persistent entity, nhưng lại muốn quay lại trạng thái ban đầu thì có thể sử dụng EntityManager#refresh, Hibernate thực hiện truy vấn và lấy dữ liệu từ database ghi đè lên các giá trị hiện có của entity. Hay một trường hợp khác chúng ta có một transaction tồn tại trong một thời gian rất lâu, trong khoảng thời gian đó có thể các luồng khác của ứng dụng đã thay đổi giá trị của một entity đang được nắm giữ, sử dụng EntityManager#refresh trong trường hợp này có thể giúp chúng ta cập nhật được giá trị mới nhất của entity.

Với CasCade.REFRESH các related entity cũng bị ghi đè giá trị từ database.

User user = new User();
user.setName("NAME A");
Order order = new Order();
order.setDate(LocalDate.of(2020, 1, 1));
order.setUser(user);
user.getOrders().add(order);
em.persist(user);
em.persist(order);
em.flush();

user.setName("NAME B");
order.setDate(LocalDate.of(2020, 2, 1));
System.out.println(user.getName());
System.out.println(order.getDate());
em.refresh(user);
System.out.println(user.getName());
System.out.println(order.getDate());

Output

NAME B
2020-02-01
Hibernate: 
    /* load entities.Order */ select
        order0_.id as id1_0_0_,
        order0_.date as date2_0_0_,
        order0_.user_id as user_id3_0_0_ 
    from
        orders order0_ 
    where
        order0_.id=?
Hibernate: 
    /* load entities.User */ select
        user0_.id as id1_1_1_,
        user0_.name as name2_1_1_,
        orders1_.user_id as user_id3_0_3_,
        orders1_.id as id1_0_3_,
        orders1_.id as id1_0_0_,
        orders1_.date as date2_0_0_,
        orders1_.user_id as user_id3_0_0_ 
    from
        user user0_ 
    left outer join
        orders orders1_ 
            on user0_.id=orders1_.user_id 
    where
        user0_.id=?
NAME A
2019-12-31

Cascade.ALL

Cascade.ALL là cách sử dụng nhanh tất cả các Cascade được giới thiệu ở trên, kể cả các Cascade được Hibernate hỗ trợ riêng như REPLICATE, SAVE_UPDATE, LOCK.

Cascade.REPLICATE

Cascade.REPLICATE thường được dùng trong trường hợp chúng ta có nhiều hơn một data source và chúng ta muốn đồng bộ dữ liệu giữa chúng. Với Cascade.REPLICATE khi một entity được đồng bộ đến các data source khác nhau thì các related entity cũng được đồng bộ theo đó.

Ánh xạ REPLICATE có chút khác với các Cascade của JPA khi chúng ta phải sử dụng @Cascade annotation.

@OneToMany(mappedBy = "user")
@Cascade(REPLICATE)
private Set<Order> orders = new HashSet<>();
User user = new User();
user.setId(1L);
user.setName("NAME A");

Order order = new Order();
order.setId(1L);

order.setUser(user);
user.getOrders().add(order);

em.unwrap(Session.class).replicate(user, ReplicationMode.OVERWRITE);

Output

Hibernate: 
    /* insert entities.Order
        */ insert 
        into
            orders
            (date, user_id, id) 
        values
            (?, ?, ?)
Hibernate: 
    /* update
        entities.User */ update
            user 
        set
            name=? 
        where
            id=?

Cascade.SAVE_UPDATE

Hoạt động tương tự như Casecade.PERSISTCasecase.MERGE, khi một entity được tạo mới và lưu xuống database hay được cập nhật thì các related entity liên quan với nó cũng được lưu và cập nhật theo.

Cascade.LOCK

Cascade.LOCK được dùng để chuyển các detached entity và các related entity của nó vào lại persistence context. 

User user = new User();
Order order = new Order();
order.setUser(user);
user.getOrders().add(order);
em.persist(user);
em.persist(order);
em.flush();
System.out.println("Before lock");
System.out.println("User belong to Persistence context: " + em.contains(user));
System.out.println("User belong to Persistence context: " + em.contains(order));

em.detach(user);
em.detach(order);

em.unwrap(Session.class)
        .buildLockRequest(new LockOptions(LockMode.NONE))
        .lock(user);

System.out.println("After lock");
System.out.println("User belong to Persistence context: " + em.contains(user));
System.out.println("User belong to Persistence context: " + em.contains(order));

Output

Before lock
User belong to Persistence context: true
User belong to Persistence context: true
After lock
User belong to Persistence context: true
User belong to Persistence context: true

Tóm lược

Như vậy là chúng ta đã tìm hiểu được tất cả các Cascade mà JPA – Hibernate hỗ trợ giúp đơn hóa quá việc thao tác với các dữ liệu quan hệ. Thử nghĩ nếu không có Cascade thì chúng ta sẽ phải làm thủ công mỗi khi thao tác trên một entity và đồng bọn của chúng.

Ngoài ra về cơ bản có khá nhiều Cascade, nhưng mình thấy sử dụng nhiều là PERSIST, MERGE hoặc nhanh nhất là ALL.

Sau cùng, các bạn có thể tham khảo mã nguồn được mình công khai trên gitlab.

Nếu vẫn chưa biết cách cấu hình một project JPA – Hibermate thì bạn có thể tham khảo bài viết sau để thực hành.

Nguồn tham khảo

https://www.baeldung.com/jpa-cascade-types

https://thorben-janssen.com/hibernate-5-date-and-time/#Java_8_Support_in_Hibernate_5

A beginner’s guide to JPA and Hibernate Cascade Types

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