Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data Jpa OneToMany save child and parent entities at the same time?

This is my parent entity. Note: Removing getters, setters, lombok annotations for brevity.

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

    private String title;

    @OneToMany(mappedBy = "board")
    private Set<Story> stories = new HashSet<>();
}

Below is my child entity

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

    private String title;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "board_id")
    @JsonIgnore
    private Board board;
}

Each Board can have multiple Story, but each Story belongs to single Board.

Now somewhere in my service, I am doing this:

public void addBoard(BoardDTO boardDto){
    // create a new board object which is just a pojo
    // by copying properties from boardDto
    Board board = ...;

    // create set of stories
    List<String> defaultTitles = Arrays.asList("Todo", "In-Progress", "Testing", "Done");
    Set<Story> stories = defaultTitles.stream().map(title -> Story.builder()
            .title(title)
            // assign a reference, I know this is wrong since board here is not
            // saved yet or fetched from db, hence the question
            .board(board) 
            .build())
            .collect(Collectors.toSet());

    // This saves board perfectly, but in Story db, the foreign key column
    // board_id is null, rightfully so since call to story table was not yet done.
    Board save = boardRepository.save(Board.builder()
            .title(board.getTitle())
            .stories(stories)
            .build());
}

One approach that I can take is to save the board first without Set<Story> and then do a save on stories with this saved board set as ref. But this would need two Repository calls and code wise it wouldn't look good.

Also, the reason why I am having trouble is because until I run this code, my db is empty. That is this is a new record that we are entering for the first time. So Board table has no rows yet.

So is there anyway to do this in a single shot? For most of the other questions on stackoverflow, the board entity is already fetched from db and then they are adding child entities to it and saving it to db. But for me, the db is completely fresh and I want to add a first new parent entity and its corresponding child entities at the same time, at least code wise even if hibernate makes multiple db calls.

like image 458
theprogrammer Avatar asked Feb 02 '20 07:02

theprogrammer


People also ask

How do you save both parent and child records on saving parent record in JPA in hibernate?

You need to set cascadeType attribute when you define @OneToMany annotation on the parent. It should be set to CascadeType. PERSIST so that when a parent is persisted to database , then the child entity also gets persisted.

Can a JPA entity have multiple OneToMany associations?

You can have multiple one-to-many associations, as long as only one is EAGER.

How do you update parent and child records in JPA?

My parent class : @Entity @DynamicUpdate @Table(name = "person") public class Person { @Id @GeneratedValue(strategy = GenerationType. IDENTITY) @Column(name="person_pk", nullable=false) private Long personPK; @OneToMany(fetch = FetchType. EAGER, mappedBy = "person",cascade = CascadeType.

What is the difference between save and saveAndFlush in JPA?

Save and saveAndFlush both can be used for saving entities. They both are both belong to the Spring data library. save may or may not write your changes to the DB straight away. When we call saveAndFlush system are enforcing the synchronization of your model state with the DB.


1 Answers

Yes, you simply need to cascade the changes from the parent to the child:

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

    private String title;

    @OneToMany(mappedBy = "board", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Set<Story> stories = new HashSet<>();
}

Now whenever you save the parent (Board), the changes will cascade to the child table. You can also use CascadeType.ALL instead of {CascadeType.PERSIST, CascadeType.MERGE} to cascade any changes, like removal (when you remove the child from the collection on the parent entity, the joining id in the child table will be removed).

like image 140
Andronicus Avatar answered Sep 21 '22 10:09

Andronicus