java - 如何使用 Spring Boot Data JPA 在一对多映射的子实体中设置parentId

标签 java hibernate spring-boot jpa spring-data-jpa

用例:我们有一对多双向关系,我们将收到作为父项进行更新的请求,其中包含正在修改的子项或未修改的子项。

技术栈

  • Spring 启动 2.0.2
  • Spring 数据 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);
}

带有业务案例的主要类(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 设置父 id,如果我们打印 SQL 日志,我们可以观察到 id 1 的子实体被删除,并且 id 3 的子实体被插入,这是预期的。

作为一种解决方法,我正在迭代所有子条目,如果未设置父条目,则手动设置。我不需要这个额外的更新声明。

尝试了其他方法,使用同步的removeChild方法删除所有子条目,然后使用同步的addChild方法一一添加剩余的子条目。这导致唯一约束失败异常。

需要什么? 在执行插入语句时设置父级而不是解决方法。

最佳答案

问题出在这部分:

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

您永远不需要重写托管集合。

现在,根据您的设计:

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.

如果客户端向您发送条目集合,您需要do the matching yourself ,这意味着您需要:

  • 添加新元素
  • 删除不再需要的元素
  • 更新现有的

关于java - 如何使用 Spring Boot Data JPA 在一对多映射的子实体中设置parentId,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53019285/

相关文章:

java - Neo4j 查询在浏览器中运行良好,但在 Spring Data Neo4j 中运行不佳

java - 随机的不行

hibernate - Grails Db反向工程生成可序列化而不是时间戳

java - 具有多个错误消息的 Spring hibernate 类型验证

java - 如何避免在成功的 Hibernate 保存后耗尽 Java 堆空间?

java - 为什么Spring Boot WEB响应速度更快?

spring-boot - Spring Boot : Turn off the console logs to the file '/var/log/app.log'

java - Android 将字节数组转换为位图的最佳方法?

java - Javers 将字段与注释进行比较

java - 创建两个线程,一个显示奇数和其他偶数