JPA + @OneToMany + DELETE:如果我稍后访问父级,则不会删除项目

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

我有一个 Deal 可以有多个 DealItems.

DealItemsDeal 中使用以下 JPA 注释链接:

public class DealEntity extends BasicEntity {

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

这是DealItem中的关系:

public class DealItemEntity extends BasicEntity {

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

当我删除一个DealItem时,它会被删除并再次保留,当我在删除后访问Deal时,请看这里:

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);
}

编辑:

这是 getFullDealResponse()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;
}

这是deleteDealItem()delete()函数:

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);
}

当我切换 CascadeType 时可以解决这个问题吗?如果可以,哪种类型是正确的?或者我是否必须迭代 Deal.getItems(),删除不需要的项目,使用 Deal.setItems() 设置新列表并仅更新 Deal 以便传播删除?

执行此操作的首选方法是什么?

I have replicated this code locally and verified my explanation

总结:

  1. 级联没有影响。即使你去掉你的级联操作,把每一项单独保存,那么当你来到这个方法的时候,它不会删除你的项。

  2. 无论 deal.getItems 初始化如何都具有相同的行为,除了直接删除 dealItem 之外,您还必须通过从 deal.getItems 中删除它来删除 dealItem .

  3. 在双向关系中,您必须明确管理双方。完全相同的方式,您将 dealItem 添加到 deal 并在保存之前设置 dealItem 的 deal 字段。

总体解释

  • JPA 只能有一个与其会话关联的特定项目的表示。

  • 它是提供Repeatble Read, Dirty Checking等的基础

  • JPA 还跟踪与其会话关联的每个对象,如果任何被跟踪的对象发生更改,它们将在事务提交时刷新。

  • 当只有 deal 对象(带有惰性 deaItems 集合)和直接获取的 dealItem 是唯一与会话关联的两个实体时,JPA 具有会话中每个演示文稿,因为没有冲突,当您删除它时,它会通过 dealItemControl.deleteDealItem 删除它 dealItem is deleted

  • 但是,一旦您调用 deal.getItems,JPA 不仅管理交易,而且还管理与 deal 对象相关联的每个 个交易项 .因此,当您删除 dealItemControl.deleteDealItem 时,JPA 会出现问题,因为 deal.getItems 告诉它未标记为删除。所以删除没有发出。

参考:生成的JPA QL也证实了我的解释

1. deal.getItems 和生成的查询

@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。没有 deal.getItems 和生成的查询

@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=?