简体   繁体   中英

Spring-Data-Jpa Cascading from child to parent

Let say I have an app to handle a collection of books.

My app allow to add a new book to the library. When creating the book, user can select the Author in the list, and if the author doesn't exist yet, he's able to add him to the list, providing his name to a form field. When the form is filled, data are sent to a WS, something like

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

Then I map json into my entity which would be

Book :

@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;
}

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;
}

This will not work as if user tries to add a new author, when I'll try to .save() I'll get an error :

org.hibernate.TransientPropertyValueException: object references an unsaved transient instance

Is there a way to handle the case with Spring-Data-Jpa, or do I have to check manually that I got an author id in the json, and if not - meaning that this is a new author - mannually run the author creation and then save the new book?

Thx!

As you're guessing, and as the Javadoc says, cascade operations that must be cascaded to the target of the association". However, be sure you understand that the mappedBy defines the owning entity of the relationship. The owning entity is the entity that actually does the persisting operations, unless overridden by a cascade setting. In this case Child is the owning entity.

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

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

The cascade setting on the Parent works when you create a Set of children and set it into the Parent and then save the Parent . Then the save operation will cascade from the Parent to the children . This is a more typical and the expected use case of a cascade setting. However, it does cause database operations to happen auto-magically and this is not always a good thing.

A Cascade setting on the child will happen when the child is persisted, so you could put a cascade annotation there, but read on ...

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

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

You will persist both the parent and the child by persisting the child.

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

and when you delete the child it will delete both the parent and the child.

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

this is where you have problems. What happens if you have more than one child?

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();

All well and nice until you delete one of the children

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

then you will get an integrity constraint violation when the cascade propagates to the Parent but there is still a second Child in the database. Sure you could cure it by deleting all the children in a single commit but that's getting kind of messy isn't it?

Conceptually people tend to think that propagation goes from Parent to Child and so it is very counterintuitive to have it otherwise. Further, what about a situation where you don't want to delete the author just because the store sold all his or her books? In this case you might be mixing cascade, sometimes from child to parent and in other cases from parent to child.

Generally I think it is better to be very precise in your database code. It's much easier to read, understand, and maintain code that specifically saves the parent first then the child or children than to have an annotation somewhere else that I may or may not be aware of that is doing additional database operations implicitly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM