Reputation: 2937
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
Reputation: 8203
I have replicated this code locally and verified my explanation
Summary:
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.
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.
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