我已经使用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
中的关系也没有更新你能告诉我为什么吗?
谢谢。
答案 0 :(得分:2)
映射@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