JPA + @OneToMany + DELETE: элемент не удаляется, если я обращусь к родителю позже - PullRequest
2 голосов
/ 21 июня 2020

У меня есть Deal, который может иметь несколько DealItems.

DealItems связаны в Deal со следующей аннотацией 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, чтобы он распространял удаление?

Каков предпочтительный способ сделать это?

1 Ответ

1 голос
/ 21 июня 2020

Я воспроизвел этот код локально и проверил свое объяснение

Резюме:

  1. Каскад не влияет . Даже если вы удалите каскадную операцию, сохраните каждый элемент отдельно, тогда, когда вы перейдете к этому методу, он не удалит ваш элемент.

  2. Чтобы иметь такое же поведение независимо от deal.getItems инициализации , Вам нужно будет удалить dealItem, удалив его из deal.getItems, в дополнение к удалению объекта dealItem напрямую.

  3. В двунаправленных отношениях вам придется явно управлять обе стороны. Точно так же вы добавляете dealItem для сделки, а также устанавливаете поле deal для dealItem перед сохранением.

Общее объяснение

  • JPA может иметь только одно представление конкретного элемента, связанного с его сеансом.

  • Это основа для обеспечения Repeatble Read, Dirty Checking et c.

  • JPA также отслеживает каждый объект, связанный с его сеансом, и если какой-либо из отслеживаемых объектов имеет изменения, они будут сброшены после подтверждения транзакции.

  • Когда только объект deal (с ленивой deaItems коллекцией) и напрямую извлеченный dealItem являются единственными двумя сущностями, связанными с сеансом, тогда JPA имеет по одной презентации для каждого в сеансе, поскольку нет конфликт, когда вы удаляете его, он удаляет его через dealItemControl.deleteDealItem dealItem: удалено

  • Однако, как только вы звоните deal.getItems, JPA не только управляет сделкой , но также каждые dealItem, связанный с объектом 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=?
...