从子级到父级的Spring-Data-Jpa级联

时间:2018-11-13 16:31:38

标签: java hibernate jpa spring-data-jpa

假设我有一个可以处理藏书的应用。

我的应用允许将新书添加到图书馆。创建书籍时,用户可以在列表中选择“作者”,如果还不存在该作者,则可以将其添加到列表中,并将其姓名提供给表单字段。 填写表单后,数据将发送到WS,例如

{ 
  "name" : "The Book name"
  "author" : {
     "name" : "author's name"
   }
}

然后我将json映射到我的实体中

书:

@Entity
@Table(name = "book")
public class Book{
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Author author;
}

作者

@Entity
@Table(name = "author")
public class Author{
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "author", cascade = { CascadeType.ALL })
    private List<Book> books;
}

这将无法正常工作,就像用户尝试添加新作者一样。当我尝试.save()时,我会收到一条错误消息:

  

org.hibernate.TransientPropertyValueException:对象引用了   未保存的瞬时实例

是否有一种方法可以处理Spring-Data-Jpa的情况,还是我必须手动检查json中是否有作者ID,如果不是,则表示这是新作者-手动运行创建作者,然后保存新书?

谢谢!

1 个答案:

答案 0 :(得分:1)

您正在猜测,正如Javadoc所说,cascade操作必须级联到关联目标。”但是,请确保您了解mappedBy定义了拥有实体拥有实体是实际执行持久操作的实体,除非被级联设置覆盖。在这种情况下,Child是拥有实体。

@Entity
public class Parent {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy="parent")
    private Set<Child> children;

在创建Parent子级并将其设置为Set然后保存Parent时,Parent上的级联设置起作用。然后,保存操作将从Parentchildren级联。这是cascade设置的更典型的预期使用案例。但是,它确实会导致数据库操作自动发生,这并不总是一件好事。

当孩子坚持下来时,将在孩子上进行级联设置,因此您可以在其中放置cascade批注,但请继续阅读...

@Entity
public class Child {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(cascade=CascadeType.ALL)
    private Parent parent;

通过保留孩子,您将同时保留父母和孩子。

tx.begin();
Parent p = new Parent();
Child c = new Child(); 
c.setParent(p);
em.persist(c);
tx.commit();

当您删除子级时,它会同时删除父级和子级。

tx.begin();
Child cFound = em.find(Child.class, 1L);
em.remove(cFound);
tx.commit();
em.clear();

这是您遇到的问题。如果您有多个孩子会怎样?

em.clear();
tx.begin();
p = new Parent();
Child c1 = new Child(); 
Child c2 = new Child(); 
c1.setParent(p);
c2.setParent(p);
em.persist(c1);
em.persist(c2);
tx.commit();

一切顺利,直到您删除children之一

em.clear();
tx.begin();
cFound = em.find(Child.class, 2L);
em.remove(cFound);
tx.commit();

然后,当级联传播到integrity constraint violation时,您将得到一个Parent,但数据库中还有第二个Child。当然可以通过一次提交删除所有子级来治愈它,但这有点混乱了吗?

从概念上讲,人们倾向于认为传播是从ParentChild的,因此以其他方式传播是非常违反直觉的。此外,如果您不想仅仅因为商店出售了他或她的所有书就删除作者,该怎么办?在这种情况下,您可能会混合级联,有时是从子级到父级,在其他情况下是从父级到子级。

通常,我认为最好在数据库代码中保持精确。比起我可能会或可能不会意识到的其他地方隐式地执行其他数据库操作,要更容易阅读,理解和维护专门先保存父项然后再保存孩子的代码。