Persistence top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll be discussing the difference between two of the options we have for removing entities from our databases when working with JPA.

First, we'll start with CascadeType.REMOVE which is a way to delete a child entity or entities when the deletion of its parent happens. Then we'll take a look at the orphanRemoval attribute, which was introduced in JPA 2.0. This provides us with a way to delete orphaned entities from the database.

Throughout the tutorial, we'll be using a simple online store domain to demonstrate our examples.

2. Domain Model

As mentioned earlier, this article makes use of a simple online store domain. Wherein the OrderRequest has a ShipmentInfo and a list of LineItem.

Given that, let's consider:

  • For the removal of ShipmentInfo, when the deletion of an OrderRequest happens, we'll use CascadeType.REMOVE
  • For the removal of a LineItem from an OrderRequest, we'll use orphanRemoval

First, let's create a ShipmentInfo entity:

@Entity
public class ShipmentInfo {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    // constructors
}

Next, let's create a LineItem entity:

@Entity
public class LineItem {

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

    private String name;

    @ManyToOne
    private OrderRequest orderRequest;

    // constructors, equals, hashCode
}

Lastly, let's put it all together by creating an OrderRequest entity:

@Entity
public class OrderRequest {

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

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

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

    // constructors

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

It's worth highlighting the removeLineItem method, which detaches a LineItem from an OrderRequest.

3. CascadeType.REMOVE

As stated earlier, marking a reference field with CascadeType.REMOVE is a way to delete a child entity or entities whenever the deletion of its parent happens.

In our case, an OrderRequest has a ShipmentInfo, which has a CascadeType.REMOVE

To verify the deletion of ShipmentInfo from the database when the deletion of an OrderRequest happens, let's create a simple integration test:

@Test
public void whenOrderRequestIsDeleted_thenDeleteShipmentInfo() {
    createOrderRequestWithShipmentInfo();

    OrderRequest orderRequest = entityManager.find(OrderRequest.class, 1L);

    entityManager.getTransaction().begin();
    entityManager.remove(orderRequest);
    entityManager.getTransaction().commit();

    Assert.assertEquals(0, findAllOrderRequest().size());
    Assert.assertEquals(0, findAllShipmentInfo().size());
}

private void createOrderRequestWithShipmentInfo() {
    ShipmentInfo shipmentInfo = new ShipmentInfo("name");
    OrderRequest orderRequest = new OrderRequest(shipmentInfo);

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

    Assert.assertEquals(1, findAllOrderRequest().size());
    Assert.assertEquals(1, findAllShipmentInfo().size());
}

From the assertions, we can see that the deletion of OrderRequest resulted in the successful deletion of the related ShipmentInfo as well.

4. orphanRemoval 

As stated earlier, its usage is to delete orphaned entities from the databaseAn entity that is no longer attached to its parent is the definition of being an orphan

In our case, an OrderRequest has a collection of LineItem objects where we use the @OneToMany annotation to identify the relationship. This is where we also set the orphanRemoval attribute to true. To detach a LineItem from an OrderRequest, we can use the removeLineItem method that we previously created.

With everything in place, once we use the removeLineItem method and save the OrderRequest, the deletion of the orphaned LineItem from the database should happen.  

To verify the deletion of the orphaned LineItem from the database, let's create another integration test:

@Test
public void whenLineItemIsRemovedFromOrderRequest_thenDeleteOrphanedLineItem() {
    createOrderRequestWithLineItems();

    OrderRequest orderRequest = entityManager.find(OrderRequest.class, 1L);
    LineItem lineItem = entityManager.find(LineItem.class, 2L);
    orderRequest.removeLineItem(lineItem);

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

    Assert.assertEquals(1, findAllOrderRequest().size());
    Assert.assertEquals(2, findAllLineItem().size());
}

private void createOrderRequestWithLineItems() {
    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(lineItems);

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

    Assert.assertEquals(1, findAllOrderRequest().size());
    Assert.assertEquals(3, findAllLineItem().size());
}

Again, from the assertions, it shows that we have successfully deleted the orphaned LineItem from the database.

Additionally, it's worth mentioning that the removeLineItem method modifies the list of LineItem instead of reassigning a value to it. Doing the latter will lead to a PersistenceException.

To verify the stated behavior, let's create a final integration test:

@Test(expected = PersistenceException.class)
public void whenLineItemsIsReassigned_thenThrowAnException() {
    createOrderRequestWithLineItems();

    OrderRequest orderRequest = entityManager.find(OrderRequest.class, 1L);
    orderRequest.setLineItems(new ArrayList<>());

    entityManager.getTransaction().begin();
    entityManager.merge(orderRequest);
    entityManager.getTransaction().commit();
}

5. Conclusion

In this article, we've explored the difference between CascadeType.REMOVE and orphanRemoval using a simple online store domain. Also, in order to verify the entities were deleted correctly from our database, we created several integration tests.

As always, the full source code of the article is available over on GitHub.

Persistence bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
guest
0 Comments
Inline Feedbacks
View all comments