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