Как установить parentId в дочернем объекте сопоставления один ко многим с использованием Spring Boot Data JPA - PullRequest
0 голосов
/ 27 октября 2018

Вариант использования: У нас одно-много двунаправленных отношений, и мы будем получать запросы на обновление в качестве родителя, который содержит либо модифицируемый дочерний элемент, либо нет.

Технологический стек

  • Spring boot 2.0.2
  • Пружинные данные Jpa

Код образца:

Сущность родительского класса:

package com.example.demo.model;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@DynamicInsert
@DynamicUpdate
@Entity
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String a;

    private String b;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "parent")
    private Set<Child> childs = new HashSet<>();

    public void addChild(Child child) {
        childs.add(child);
        child.setParent(this);
    }

    public void removeChild(Child child) {
        childs.remove(child);
        child.setParent(null);
    }

    public void setChilds(
            Set<Child> childrens) {
        if (this.childs == null) {
            this.childs = childrens;
        }
        else {
            this.childs.retainAll(childrens);
            this.childs.addAll(childrens);
        }
    }

}

Детский класс

package com.example.demo.model;

import java.util.Objects;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@DynamicInsert
@DynamicUpdate
@Entity
@Table(name = "child", uniqueConstraints = {
        @UniqueConstraint(columnNames = { "a", "b", "c", "parent_id" }) })
public class Child {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String a;

    private String b;

    private String c;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Parent parent;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Child)) {
            return false;
        }
        Child that = (Child) o;
        return Objects.equals(getA(), that.getA()) && Objects.equals(getB(), that.getB())
                && Objects.equals(getC(), that.getC());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getA(), getB(), getC());
    }

}

Класс репозитория:

package com.example.demo.model;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {

    @Query("select p from Parent p join fetch p.childs where p.a = ?1")
    Parent findByA(String a);
}

Main-Class с бизнес-кейсом:

package com.example.demo;

import java.util.HashSet;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.util.Assert;

import com.example.demo.model.Child;
import com.example.demo.model.Parent;
import com.example.demo.model.ParentRepository;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    @Autowired
    ParentRepository repository;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        Child c1 = new Child();
        c1.setA("a1");
        c1.setB("b1");
        c1.setC("c1");
        Child c2 = new Child();
        c2.setA("a2");
        c2.setB("b2");
        c2.setC("c2");
        Parent p = new Parent();
        p.addChild(c1);
        p.addChild(c2);
        p.setA("a");

        repository.save(p);

        // This works till now

        // We will get the request for updating parent which might contain removal or addition of the child

        Parent retrievedParent = repository.findByA("a");
        retrievedParent.setB("b");

        Child c4 = new Child();
        c4.setA("a2");
        c4.setB("b2");
        c4.setC("c2");
        Child c3 = new Child();
        c3.setA("a3");
        c3.setB("b3");
        c3.setC("c3");

        //If we know that c1 is removed and c3 is added we can use synchronize methods written in Parent
        //As we don't know which are removed and which are added also as we won't get the id from request passing them 
        // directly to set to let hibernate handle it as equals and Hashcode is already written.

        Set<Child> childrens = new HashSet<>();
        childrens.add(c3);
        childrens.add(c4);
        retrievedParent.setChilds(childrens);

        Parent persistedParent = repository.save(retrievedParent);
        for (Child child : persistedParent.getChilds()) {
            Assert.notNull(child.getParent(), "Parent must not be null");
            //For child 3 it is failing
        }
    }


}

С помощью приведенного выше кода невозможно установить родительский идентификатор для дочерней сущности 4, если мы печатаем журналы SQL, мы можем наблюдать, что дочерний элемент с идентификатором 1 удален, а дочерний элемент с идентификатором 3 вставлен, что ожидается.

В качестве обходного пути я перебираю все дочерние записи, и если родительский элемент не установлен, то устанавливаю вручную. Я не хочу этого дополнительного заявления об обновлении.

Пробовали другие подходы, удаляя все дочерние записи, используя синхронизированный метод removeChild, а затем добавляя оставшиеся по одному, используя синхронизированный метод addChild. Это вызывает уникальное исключение сбоя ограничения.

Что требуется? Установка родителя вместо обходного пути в операторе вставки при его выполнении.

1 Ответ

0 голосов
/ 27 октября 2018

Проблема возникает из этой части:

Set<Child> childrens = new HashSet<>();
childrens.add(c3);
childrens.add(c4);
retrievedParent.setChilds(childrens);

Вам никогда не нужно переписывать управляемую коллекцию.

Теперь, основываясь на вашем дизайне:

Если мы знаем, что c1 удален, а c3 добавлен, мы можем использовать методы синхронизации, написанные на Parent.

Как мыне знаю, какие из них удалены, а какие добавлены, так как мы не получим идентификатор из запроса, передавая их напрямую, чтобы установить, чтобы hibernate обрабатывал его как равный, и хэш-код уже записан.

Есликлиент отправляет вам коллекцию записей, вам нужно сделать сопоставление самостоятельно, что означает, что вам необходимо:

  • добавить новые элементы
  • удалить элементы, которые больше не нужны
  • обновить существующие

Для получения более подробной информации, ознакомьтесь с этой статьей .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...