Felix Hagspiel
Felix Hagspiel

Reputation: 2937

JPA + @OneToMany + DELETE: Item is not deleted if I access parent later

I got a Deal which can have multiple DealItems.

The DealItems are linked in the Deal with the following JPA annotation:

public class DealEntity extends BasicEntity {

@OneToMany(mappedBy = "deal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DealItemEntity> items;
...

This is the relation inside a DealItem:

public class DealItemEntity extends BasicEntity {

@ManyToOne
@JoinColumn(name = "deal_id", nullable = false)
private DealEntity deal;
...

When I delete a DealItem it is deleted and persited again, when when I access the Deal after the deletion, see here:

public FullDealResponse deleteDealItem(final String dealCode, final long dealItemId) {

    DealEntity dealEntity = dealControl.findDealByDealCode(dealCode);

    if (dealEntity == null) {
        throw new WorkbenchGenericErrorException("Deal not found");
    }

    DealItemEntity dealItemEntity = dealItemControl.findDealItemByIdAndDealId(dealItemId, dealEntity.getId());
    if (dealItemEntity == null) {
        throw new WorkbenchGenericErrorException("Deal item not found");
    }
    // this makes a database DELETE call that is executed after the session is done
    dealItemControl.deleteDealItem(dealItemEntity);
    
    // When I remove this and I do not return anything, the deletion works
    return this.getFullDealResponse(dealEntity);
}

EDIT:

This is getFullDealResponse() and getFullDealItemResponse():

private FullDealResponse getFullDealResponse(final DealEntity dealEntity) {
    FullDealResponse response = new FullDealResponse();
    response.setDescription(dealEntity.getDescription());
    response.setTitle(dealEntity.getTitle());
    response.setDealCode(dealEntity.getDealCode());
    response.setCreatedAt(dealEntity.getCreatedAt());
    response.setUpdatedAt(dealEntity.getUpdatedAt());

    // get related items
    List<FullDealItemResponse> itemsResponse = new ArrayList<FullDealItemResponse>();
    for (DealItemEntity dealItemEntity : dealEntity.getItems()) {
        itemsResponse.add(this.getFullDealItemResponse(dealItemEntity));
    }
    response.setItems(itemsResponse);
    return response;
}


private FullDealItemResponse getFullDealItemResponse(final DealItemEntity dealItemEntity) {
    FullDealItemResponse response = new FullDealItemResponse();
    response.setId(dealItemEntity.getId());
    response.setDescription(dealItemEntity.getDescription());
    response.setTitle(dealItemEntity.getTitle());
    response.setCreatedAt(dealItemEntity.getCreatedAt());
    response.setUpdatedAt(dealItemEntity.getUpdatedAt());

    return response;
}

This is deleteDealItem() and delete() function:

public void deleteDealItem(final DealItemEntity dealItemEntity) {
        super.delete(DealItemEntity.class, dealItemEntity.getId());
    }
protected void delete(final Class<?> type, final Object id) {
    Object ref = this.em.getReference(type, id);
    this.em.remove(ref);
}

Can this be solved when I switch the CascadeType, and if so, which would be the correct type? Or would I have to iterate over Deal.getItems(), remove the unwanted item, set the new list with Deal.setItems() and update only the Deal so it propagates the deletion?

What is the preferred way to do this?

Upvotes: 2

Views: 2607

Answers (1)

I have replicated this code locally and verified my explanation

Summary:

  1. Cascade does not have impact. Even if you remove your cascade operation, save each item separately , then when you come to this method, it will not delete your item.

  2. To have same behaviour regardless of deal.getItems initialisation, You will have to delete the dealItem by removing it from deal.getItems in addition to deleting the dealItem directly.

  3. On a bi-directional relationship, you will have to explicitly manage both sides. Exactly the same way, you add the dealItem to deal as well set deal field of dealItem before you save.

Overall Explanation

  • JPA can have only one representation of a particular item associated with it's session.

  • It is the foundation for providing Repeatble Read, Dirty Checking etc.

  • JPA also tracks every object associated with its session and If any of the tracked objects have changes, they will flushed when the transaction committed.

  • When only deal object (with lazy deaItems collection) and the directly fetched dealItem are the only two entities associated with the session, then JPA has one presentation for each in the session, since there is no conflict, when you delete it, it deletes it via dealItemControl.deleteDealItem the dealItem is deleted

  • However, once you call deal.getItems, JPA not only manages deal, but also every dealItem associated with the deal object. So when when you delete the dealItemControl.deleteDealItem, JPA has an issue because deal.getItems tells it is not marked for delete. So the delete is not issued.

Reference: JPA QL generated also confirms my explanation

1. With deal.getItems and Queries Generated

@OneToMany(mappedBy = "deal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DealItemEntity> items; 
DealEntity dealEntity = dealControl.findDealByDealCode(dealCode);
....
dealItemControl.deleteDealItem(dealItemEntity);
....
dealEntity.getItems()
select deal0_.* from deal deal0_  where deal0_.id=?

select  dealitem0_.* 
        deal1_.*
    from
        deal_item dealitem0_ inner join deal deal1_  on dealitem0_.deal_id=deal1_.id 
    where
        dealitem0_.id=?

select items0_.* from deal_item items0_  where items0_.deal_id=?

2. Without deal.getItems and Queries Generated

@OneToMany(mappedBy = "deal", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DealItemEntity> items; 
DealEntity dealEntity = dealControl.findDealByDealCode(dealCode);
....
dealItemControl.deleteDealItem(dealItemEntity);

select deal0_.* from deal deal0_  where deal0_.id=?


select  dealitem0_.* 
        deal1_.*
    from
        deal_item dealitem0_ inner join deal deal1_  on dealitem0_.deal_id=deal1_.id 
    where
        dealitem0_.id=?

delete from deal_item  where id=?

Upvotes: 2

Related Questions