JPA CascadeType.REMOVE và orphanRemoval dùng để làm gì?

Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu về 2 option dùng để xóa các entity khỏi database khi sử dụng JPA.

Đầu tiên là CascadeType.REMOVE là option dùng trong relationship-mapping của JPA, cho phép xóa các entity con khi entity cha của nó bị xóa đi. Còn thuộc tính orphanRemoval được giới thiệu từ phiên bản JPA 2.0 cho phép xóa các entity khi chúng không còn liên kết đến mội entity nào nữa.

Để hiểu rõ hơn về 2 tùy chọn này chúng ta sẽ đi qua từng phần và lấy ví dụ cụ thể để hiểu cách hoạt động của chúng.

Chuẩn bị

Trước khi đi qua các ví dụ thì chúng ta cần chuẩn bị trước một số thứ sau

Maven dependency

Chúng ta cần sử dụng gói JPA-Hibernate và H2 database để thực hành trong ví dụ này. Lưu ý là chúng ta sẽ dùng H2 lưu trên memory nên sau khi tắt ứng dụng dữ liệu cũng sẽ mất theo.

<dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.0.0.Final</version>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>

</dependencies>

Domain model

Trong bài viết này chúng ta sẽ tạo ứng dụng cửa hàng online đơn giản với 3 entity chính là OrderRequest, ShipmentInfo và  LineItem. 

Trong đó

  • OrderRequest có mối quan hệ 1-1 với ShipmentInfo
  • OrderRequest có mối quan hệ 1-N với ShipmLineItementInfo
@Entity
@NoArgsConstructor
@Setter
@Getter
public class OrderRequest {

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

    @OneToOne(cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
    private ShipmentInfo shipmentInfo;

    @OneToMany(orphanRemoval = true, cascade = CascadeType.PERSIST, mappedBy = "orderRequest")
    private List<LineItem> lineItems;

    public void removeLineItem(LineItem lineItem) {
        lineItems.remove(lineItem);
    }
}

Trong OrderRequest class chúng ta sẽ dùng CascadeType.REMOVE khi mapping OneToOne với với ShipmentInfo. Và thuộc tính orphanRemoval khi mapping OneToMany với LineItem.

Điều này có nghĩa là:

  • Khi xóa 1 entity OrderRequest thì ShipmentInfo liên kết tới nó cũng tự động bị xóa khỏi database
  • Khi chúng ta dùng OrderRequest#removeLineItem để ngắt liên kết giữa OrderRequest và một danh sách LineItem  thì chúng cũng sẽ tự động bị xóa khỏi database
@Entity
@NoArgsConstructor
@Getter
@Setter
public class ShipmentInfo {

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

    private String name;

}
@Entity
@NoArgsConstructor
@Setter
@Getter
public class LineItem {

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

    private String name;

    @ManyToOne
    private OrderRequest orderRequest;

    public LineItem(String name) {
        this.name = name;
    }
}

CascadeType.REMOVE

Sau khi mọi thứ đã sẵn sàng thì bây giờ chúng ta sẽ tiến hành thí nghiệm cách hoạt động của Cascade.REMOVE. Chúng ta chỉ cần tạo ra một OrderRequest và ShipmentInfo entity sau đó lưu xuống database, tiếp theo truy vấn OrderRequest và xóa nó khỏi database. Kết quả chúng ta mong muốn là ShipmentInfo entity cũng sẽ bị xóa khỏi database.

public class Example1 {

    static EntityManagerFactory emf = Persistence.createEntityManagerFactory("removal");

    public static void main(String[] args) {

        // init data
        createOrderRequestWithShipmentInfo();

        EntityManager em = emf.createEntityManager();
        OrderRequest orderRequest = em.find(OrderRequest.class, 1L);
        ShipmentInfo shipmentInfo = orderRequest.getShipmentInfo();
        System.out.println("ShipmentInfo Name: " + shipmentInfo.getName());

        // remove cascade
        em.getTransaction().begin();
        em.remove(orderRequest);
        em.getTransaction().commit();

        List<OrderRequest> orderRequests = findAllOrderRequest();
        System.out.println("OrderRequests size: " + orderRequests.size());

        List<ShipmentInfo> shipmentInfos = findAllShipmentInfo();
        System.out.println("ShipmentInfo size: " + shipmentInfos.size());
    }

    private static List<OrderRequest> findAllOrderRequest() {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<OrderRequest> cq = cb.createQuery(OrderRequest.class);
        Root<OrderRequest> root = cq.from(OrderRequest.class);
        CriteriaQuery<OrderRequest> findAll = cq.select(root);
        TypedQuery<OrderRequest> findAllQuery = em.createQuery(findAll);

        return findAllQuery.getResultList();
    }

    private static List<ShipmentInfo> findAllShipmentInfo() {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<ShipmentInfo> cq = cb.createQuery(ShipmentInfo.class);
        Root<ShipmentInfo> root = cq.from(ShipmentInfo.class);
        CriteriaQuery<ShipmentInfo> findAll = cq.select(root);
        TypedQuery<ShipmentInfo> findAllQuery = em.createQuery(findAll);

        return findAllQuery.getResultList();
    }


    private static void createOrderRequestWithShipmentInfo() {
        EntityManager em = emf.createEntityManager();
        ShipmentInfo shipmentInfo = new ShipmentInfo();
        shipmentInfo.setName("name");
        OrderRequest orderRequest = new OrderRequest();
        orderRequest.setShipmentInfo(shipmentInfo);

        em.getTransaction().begin();
        em.persist(orderRequest);
        em.getTransaction().commit();

    }
}

Sau đây là log từ màn hình console

ShipmentInfo Name: name

Hibernate: 
    /* delete entity.OrderRequest */ delete 
        from
            OrderRequest 
        where
            id=?
Hibernate: 
    /* delete entity.ShipmentInfo */ delete 
        from
            ShipmentInfo 
        where
            id=?


OrderRequests size: 0
ShipmentInfo size: 0

Các bạn có thể thấy ShipmentInfo đã bị xóa theo OrderRequest liên kết với nó bị xóa khỏi database, nhìn vào log của Hibernate chúng ta cũng có thể thấy rõ nó đã sinh thêm 1 câu query dùng để xóa ShipmentInfo.

orphanRemoval

Thuộc tính orphanRemoval được dùng trong mapping giữa OrderRequest và LineItem. Khi chúng ta ngắt kết nối giữa OrderRequest với một hoặc tất cả các ListItem liên kết tới nó thì chúng sẽ tự động bị xóa khỏi database

public class Example2 {

    static EntityManagerFactory emf = Persistence.createEntityManagerFactory("removal");

    public static void main(String[] args) {
        createOrderRequestWithLineItems();

        EntityManager em = emf.createEntityManager();

        OrderRequest orderRequest = em.find(OrderRequest.class, 1L);
        List<LineItem> lineItems = orderRequest.getLineItems();
        LineItem lineItem = lineItems.get(0);
        LineItem lineItem1 = lineItems.get(1);
        LineItem lineItem2 = lineItems.get(2);
        System.out.println("LineItem 1: " + lineItem.getName());
        System.out.println("LineItem 2: " + lineItem1.getName());
        System.out.println("LineItem 3: " + lineItem2.getName());
        orderRequest.removeLineItem(lineItem1);

        em.getTransaction().begin();
        em.merge(orderRequest);
        em.getTransaction().commit();

        OrderRequest orderRequestAfterDeleteLineItem = em.find(OrderRequest.class, 1L);
        List<LineItem> list = orderRequestAfterDeleteLineItem.getLineItems();
        System.out.println("LineItem after remove: " + list.size());
    }

    private static void createOrderRequestWithLineItems() {
        EntityManager em = emf.createEntityManager();

        List<LineItem> lineItems = new ArrayList<>();
        lineItems.add(new LineItem("line item 1"));
        lineItems.add(new LineItem("line item 2"));
        lineItems.add(new LineItem("line item 3"));

        OrderRequest orderRequest = new OrderRequest();
        orderRequest.setLineItems(lineItems);
        lineItems.forEach(lineItem -> lineItem.setOrderRequest(orderRequest));

        em.getTransaction().begin();
        em.persist(orderRequest);
        em.flush();
        em.getTransaction().commit();
    }

    private static List<OrderRequest> findAllOrderRequest() {
        EntityManager em = emf.createEntityManager();

        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<OrderRequest> cq = cb.createQuery(OrderRequest.class);
        Root<OrderRequest> root = cq.from(OrderRequest.class);
        CriteriaQuery<OrderRequest> findAll = cq.select(root);
        TypedQuery<OrderRequest> findAllQuery = em.createQuery(findAll);

        return findAllQuery.getResultList();
    }

}

Kết quả màn hình console

LineItem 1: line item 1
LineItem 2: line item 2
LineItem 3: line item 3
Hibernate: 
    /* delete entity.LineItem */ delete 
        from
            LineItem 
        where
            id=?
LineItem after remove: 2

Chúng ta có thể thấy sau khi ngắt kết nối một lineItem1 khỏi OrderRequest thì nó cũng tự động bị xóa khỏi database.

Tóm lược

Qua bài viết này chúng ta đã tìm hiểu được cách hoạt động của Cascade.REMOVE và thuộc tính orphanRemoval khi mapping entity trong JPA-Hibernate. Mình rất thường xuyên dùng 2 tùy chọn này trong các dự án vì chúng giúp mình xóa các entity liên quan khỏi database một cách tự động mà nếu làm thủ công sẽ tốn khá nhiều công sức.

Mã nguồn đầy đủ các bạn có thể download tại đây: removal

Nguồn tham khảo

https://www.baeldung.com/jpa-cascade-remove-vs-orphanremoval

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