【问题标题】:Spring-Data-Jpa Cascading from child to parentSpring-Data-Jpa 从子级级联到父级
【发布时间】:2018-11-14 02:07:57
【问题描述】:

假设我有一个应用程序来处理书籍集合。

我的应用程序允许向图书馆添加一本新书。创建书籍时,用户可以在列表中选择作者,如果作者不存在,他可以将他添加到列表中,将他的姓名提供给表单字段。 填写表格后,数据会发送到 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,如果没有 - 意味着这是一个新作者 - 手动运行创建作者然后保存新书?

谢谢!

【问题讨论】:

  • 没有理由保存Author 将不起作用。您是否尝试在其中设置未保存的Author 来保存Book
  • 如果我尝试单独保存作者,然后保存这本书,是的,它会起作用,但这不是我的问题 :) 实际上我希望能够调用 bookRepository.save(newBook)里面有一个新的未保存的作者,想知道我是否可以通过 JPA 来做到这一点,通过某种级联或其他方式?

标签: java hibernate jpa spring-data-jpa


【解决方案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 时起作用。然后保存操作将从Parent 级联到children。这是cascade 设置的更典型和预期的用例。但是,它确实会导致数据库操作自动发生,这并不总是一件好事。

当孩子被持久化时,会发生对孩子的 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();

然后当级联传播到Parent 时,您将获得integrity constraint violation,但数据库中仍有第二个Child。当然,您可以通过在一次提交中删除所有子级来解决它,但这会变得有点混乱,不是吗?

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

一般来说,我认为最好在您的数据库代码中非常精确。阅读、理解和维护专门首先保存父级然后是子级或子级的代码比在其他地方有注释要容易得多,我可能知道也可能不知道隐式地执行额外的数据库操作。

【讨论】:

  • 非常感谢您的准确回答和您的时间,然后我会明确地创建作者,然后是这本书,非常感谢 :)
猜你喜欢
  • 2017-10-25
  • 1970-01-01
  • 2020-08-27
  • 2022-11-28
  • 1970-01-01
  • 2017-08-31
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
相关资源
最近更新 更多