SpringBoot JPA Many2One,One2Man不能以两种方式正常工作

时间:2018-12-13 21:56:48

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

我已经使用JPA和SpringBoot创建了一个程序,数据库是Postgresql,我有两个实体:Parent和Child:

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

  @Id
  @Column(name = "id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;
  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
  private Set<Child> children = new HashSet<>();
}

以及子实体:

@Entity
@Table(name = "child")
public class Child {
  @Id
  @Column(name = "id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  @ManyToOne
  @JoinColumn(name = "parent")
  private Parent parent;
}

然后在应用程序中,我已经自动连接了两个存储库以进行一些测试: 当我这样做时会起作用:

Child child1 = new Child("Lucas", new Date(2012, 12,12));
Parent parent1 = new Parent("Jack", "Bauer");
child1.setParent(parent1);
childRepository.save(child1);

在子表中,父ID已正确设置。

但是,如果我是从另一面创建的,它将无法正常工作:

Child child1 = new Child("Lucas", new Date(2012, 12,12));
Parent parent1 = new Parent("Jack", "Bauer");
childRepository.save(child1);
parent1.getChildren().add(child1);
parentRepository.save(parent1);

没有错误出现,并且表Child

中的关系也没有更新

你能告诉我为什么吗?

谢谢。

2 个答案:

答案 0 :(得分:2)

  1. 双向@OneToMany:

映射@OneToMany关联的最好方法是依靠@ManyToOne端传播所有实体状态更改:

父类:

@OneToMany(
        mappedBy = "post", 
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<Child> childs = new ArrayList<>();

//Constructors, getters and setters removed for brevity

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

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

儿童班:

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

@ManyToOne关联使用FetchType.LAZY,因为否则,我们将退回到对性能不利的EAGER提取

父实体具有两个实用程序方法(例如addChild和removeChild),用于同步双向关联的双方。在使用双向关联时,应始终提供这些方法,否则,您将面临非常细微的状态传播问题。

用于测试:

Parent parent1=new Parent();
// set datas into parent1 and to put childs we can use the utility method addChild
parent1.addChild(new Child(datas...))
parent1.addChild(new Child(datas...)) //etc
parentRepository.save(parent1);

答案 1 :(得分:2)

您遇到的问题是,当向Cascade添加Child并在Parent上具有级联注释时,Parent操作为何无法工作。

通常,关系的所有者(在这种情况下,如Child注释所指示的mappedBy="parent")负责保持关系。您已经通过Child的单向映射演示了这一点-通过ManyToOne注释完成了。

Child child = new Child();
Parent parent = new Parent();
child.setParent(parent);
parentRepo.save(parent);
childRepo.save(child);

然后,您使用Parent中的双向映射尝试了同样的事情–使用OneToMany批注完成。由于此注释包含mappedBy="parent"注释,因此它不是所有者,通常添加到Set<Child> children的所有内容都将被忽略。但是,您添加了cascade = CascadeType.ALL批注,因此这会覆盖所有权设置,并允许Parent实体根据CascadeType值确定的操作子集和特定条件的持久关系。

但是父母怎么知道要坚持哪个孩子呢?我假设它查看子实例是否已经被持久化。如果有,则不需要级联操作。当您自己持久保留子实例时,您绕过了级联操作。

Child child = new Child();
Parent parent = new Parent();
Set<Child> children = new HashSet<>();
childRepo.save(child);
children.add(child);
parent.setChildren(children);
parentRepo.save(parent);

此特定代码给我一个错误,因为子实例已保存并分离,然后要求再次保存。错误情况并不总是会发生-我认为这取决于父项是新的还是已从数据库中检索到。

  

org.hibernate.PersistentObjectException:传递给持久对象的分离实体:

因此,如果您希望Parent实体进行级联,则必须向其传递尚未保存的Child实例。请注意,您仍然必须设置孩子的父母,才能创建关系,否则父母将保留无父母的孩子。

Child child = new Child();
Parent parent = new Parent();
child.setParent(parent);
Set<Child> children = new HashSet<>();
children.add(child);
parent.setChildren(children);
parentRepo.saveAndFlush(parent);

这对我来说很好。请注意,我自己创建了Set个孩子,而不是每次实例化Parent实体时都创建它。通常,对数据库的查询要比对数据库的查询要频繁得多,对于每次查询,JPA提供程序都会将其自己的Collection类放入children的{​​{1}}属性中,因此该集合您实例化的对象通常最终会进入垃圾堆-效率低下。

Parent