сироты не удаляются после изменения родительского набора JPA 2 Hibernate 5

#java #hibernate #spring-data-jpa

Вопрос:

я, наверное, делаю что-то не так, но не могу понять, что именно. у меня есть 2 таблицы с 2 объектами со следующим отображением

 @Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "parent")
public class Parent {

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

    @JsonIgnore
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    @LazyCollection(LazyCollectionOption.FALSE)
    @Getter
    private List<ChildBase> childs;

    public Parent(){

      this.childs = new ArrayList ();

}



@Entity
@Table(name = "childs")
@Getter
@Setter
@ToString
@Accessors(chain = true)
@NoArgsConstructor
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class ChildBase {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    protected long id;

    @JoinColumn(nullable = false)
    @ManyToOne
    @ToString.Exclude
    protected Parent parent;

    public ChildBase(Parent parent){
      this.parent = parent;
      }

}


@Entity
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Getter
@Setter
@Accessors(chain = true)
public class MyChild extends ChildBase {
    @Enumerated(EnumType.STRING)
    @Column(name = "name")
    private String name;

    public MyChild(String name, Parent parent) {
        super(parent);
        this.name = name;
    }

 

проблема возникает, как только я пытаюсь отключить родителя от ребенка с помощью следующего теста:

 @Test
public void test(){

Parent parent = new Parent();
MyChild child1 = new MyChild("a", parent);
MyChild child2 = new MyChild("a", parent);
parent.getChilds.add(child1);
parentRepository.save(parent);

parent.getChilds.remove(child1);
parent.getChilds.add(child2);
parentRepository.save(parent);
}

 

я ожидаю увидеть только вторую дочернюю строку в таблице «дети», но я вижу их обоих.

Редактировать:

я также пытался ссылаться на null из дочернего файла перед вторым сохранением следующим образом: child1.SetParent(null). это тоже не помогает

Комментарии:

1. прежде чем родительский объект будет сохранен снова, вы проверили, что в списке дочерних объектов есть только объект child2.

2. конечно, я это проверил..

Ответ №1:

Это двунаправленное отношение и MyChild , соответственно ChildBase , ваша сторона владения отношением.

Чтобы инициировать удаление сироты , вы child1.setParent(null) тоже должны это сделать.

ОБНОВЛЕНИЕ: выполнила настройку, предоставленную из чата. вот классы, и это работает совершенно нормально.

 @Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "parent")
public class Parent {

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

    @JsonIgnore
    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    @LazyCollection(LazyCollectionOption.FALSE)
    @Getter
    private List<ChildBase> childs;

    public Parent(){

      this.childs = new ArrayList<>();
    }

}
 
 @Entity
@Table(name = "childs")
@Getter
@Setter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Accessors(chain = true)
@NoArgsConstructor
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class ChildBase {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @EqualsAndHashCode.Include
    protected long id;

    @JoinColumn(nullable = false)
    @ManyToOne
    @ToString.Exclude
    protected Parent parent;

    public ChildBase(Parent parent){
      this.parent = parent;
    }

}
 
 @Entity
@NoArgsConstructor
@Getter
@Setter
@Accessors(chain = true)
public class MyChild extends ChildBase {
  
    @Column(name = "name")
    private String name;

    public MyChild(String name, Parent parent) {
        super(parent);
        this.name = name;
    }
}

 

И вот вам испытание. После выполнения есть только childB в базе данных.

     @Test
    void test() {
        Parent parent = new Parent();
        MyChild child1 = new MyChild("childA", parent);
        MyChild child2 = new MyChild("childB", parent);
        parent.getChilds().add(child1);
        parentRepository.save(parent);

        parent.getChilds().remove(child1); // first remove
        child1.setParent(null);            // set parent null

        parent.getChilds().add(child2);
        parentRepository.save(parent);
    }
 

Комментарии:

1. ОК. Измените аннотацию дочерней базы с @Entity на @MappedSuperclass и добавьте @EqualsAndHashcode , но исключите parent поле.

2. добавьте @EqualsAndHashcode в ChildBase ?

3. правильный. если вы его опустите, то будут использоваться euquals java по умолчанию. но в этом случае вы должны проверять равенство только по идентификатору. дополнительно удалить @EqualsAndHashcode из MyChild

4. Ах! И я не знаю, какой случай вы тестируете, но обычно родитель сохраняется вместе с ребенком в первом действии (например, веб-запрос), а во втором действии вы удаляете первого ребенка, а второй добавляется. таким образом, между этими действиями родитель загружается из базы данных. вы должны добавить parent = parentRepository.findById(parent.getId()) , а затем удалить и добавить детей.

5. 2 вещи. во-первых, я не могу использовать MappedSuperclass аннотацию, потому что родительское сопоставление OneToMany хранит коллекцию базового класса, оно просто не работает при создании компонента EntityManagerFactory. во-вторых, я попытался использовать parentRepository.findById(parent.getId()) сразу после первого сохранения и присвоил результат отцу, как вы предложили, но происходит что-то странное.. вместо удаления первого дочернего элемента в конце теста я вижу только первого дочернего элемента в базе данных