How to save both parent and child records on saving parent record in JPA/hibernate?

Let’s say you have created an application to post blogs.

You are allowing users to comment on the blogs.

Every blog is associated with many comments.

You have chosen spring data as your backend technology.

How would you design this ?

You can create an entity class representing a blog post , say Blogpost.java

And then you can create another entity class representing a comment , say Comment.java

You then define the one to many relationship between blogpost and comment using @OneToMany annotation on the parent entity (Blogpost) and @ManyToOne annotation on the child entity (Comment).

Now let’s say you write code to create a blogpost ,add some comments to it and save it to database.

Would Hibernate save both parent and child entities on saving the parent record by default?

It won’t.

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.

Let’s look at the code:

Here is the Blogpost entity:

package com.hibernate.childrecords;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Blogpost {



    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String title;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
    List<Comment> commentList = new ArrayList<>();


    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<Comment> getCommentList() {
        return commentList;
    }

    public void setCommentList(List<Comment> commentList) {
        this.commentList = commentList;
    }

    @Override
    public String toString() {
        return "Blogpost{" +
                "id=" + id +
                " title=" + title +
                " comment=" + commentList +

                '}';
    }
}

The comment objects are associated with the Blogpost object using @OneToMany annotation.

Notice that I have added cascadeType as CascadeType.PERSIST. This will make sure both parent and child entities are persisted to database when parent is saved.

Also I have added the attribute fetch = FetchType.EAGER in the @OneToMany annotation. This is added for the comments associated with a blogpost to be fetched as soon as the blogpost entity is fetched so that they can be printed in the toString() method . If you don’t want to print the comments in the toString() method you can ignore this. By default hibernate will lazily load the child entities.

Below is the child entity :

package com.hibernate.childrecords;

import javax.persistence.*;

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String message;

    @ManyToOne
    private Blogpost blogpost;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Blogpost getBlogpost() {
        return blogpost;
    }

    public void setBlogpost(Blogpost blogpost) {
        this.blogpost = blogpost;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                "comment=" + message +
                '}';
    }
}

As you see a field to represent the parent entity Blogpost is declared at the class level and is annotated with @ManyToOne annotation. It automatically associates the current entity to the blogpost entity.

For both the entities I am letting hibernate decide what strategy to use to generate the primary key by annotating the primary key field with @GeneratedValue(strategy = GenerationType.AUTO)

In case you are manually generating the primary keys then cascadeType = CascadeType.PERSIST will not work . CascadeType.MERGE should be used in such cases . Or CascadeType.ALL can be used in both the cases as it includes all the cascade scenarios.

Let’s look at the repository code provided by Spring Data:

package com.hibernate.childrecords;

import org.springframework.data.repository.CrudRepository;

public interface BlogRepository extends CrudRepository<Blogpost,Long> {
}

As you see there is almost no code there. Spring Data abstracts away all the database operations.

Let’s look at the client code.

I have used SpringBoot’s ApplicationRunner interface to test the functionality as soon as the application starts:

package com.hibernate.childrecords;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ChildrecordsApplication implements ApplicationRunner {


    @Autowired
    private BlogRepository blogRepository;


    public static void main(String[] args) {
        SpringApplication.run(ChildrecordsApplication.class, args);
    }


    @Override
    public void run(ApplicationArguments args) throws Exception {

        Blogpost post = new Blogpost();

        post.setTitle("First post");


        Comment comment = new Comment();

        comment.setMessage("My first comment");
        comment.setBlogpost(post);


        post.getCommentList().add(comment);


        blogRepository.save(post);


        Iterable<Blogpost> blogpostList = blogRepository.findAll();


        for (Blogpost blogpost : blogpostList) {

            System.out.println(blogpost);
        }


    }
}

The code is self explanatory. I am creating a blogpost object, a comment object , adding the comment object to the blogpost object and then call save on an instance of blogpost repository object.

That saves both the blogpost and its comment to the database.

Then I am fetching all blogposts and printing them to the console.

Below is the output:

Blogpost{id=1 title=First post comment=[Comment{id=1comment=My first comment}]}


Posted

in

, , ,

by

Comments

5 responses to “How to save both parent and child records on saving parent record in JPA/hibernate?”

  1. Rye Borja Avatar
    Rye Borja

    Hi followed your tutorial but CascadeType.PERSIST does not work and I need to add CascadeType.MERGE. Why is that?

    1. Vijay SRJ Avatar
      Vijay SRJ

      Hi , What is the strategy that you are using to generate primary key?

  2.  Avatar
    Anonymous

    So I made a blockchain app. It only boots up and initializes with genesis block if I have CASCADE TYPE ALL or persist, but then when I want to save a new block that I just mined from a transaction pool (after which the transaction pool is flushed) then it only seems to work if I have it set at merge. Gosh I don’t remember what error it gives. Dialect something, something to do with same entity exists in that session. I understand a little WHAT is happening without having the slightest clue WHY it happens or why Cascade type merge fixes that (But doesn’t let me initialize the blockchain in the first place). Needing to switch is not tenable.

  3. Java Developer Avatar

    Hey,

    The post is worth reading and tried it, there is an issue I’m facing, could you please see and help?

    The combination I used is ID generation type set to IDENTITY and CASCADE type set to PERSIST, and upon saving the parent, child is also being saved and child table is also supplied with a forign key column some “xx_id”, but the issue is, the ID of the parent is not saved in that forign key column in child’s table and all enteries are “null”. What I tried so far to resolve the issue:

    1- I’ve tried both AUTO & IDENTITY to generate IDs, and also used MERGE and PERSIST as cascade types with all the possible combination.
    2- If I use a @JoinColumn annotation in child class and provide it with the name of column same as the one it creates automatically without using “@JoinColumn” and use a custom column name, it doesn’t even save child upon saving the parent.

    Any help in this regard?

    1. Vijay SRJ Avatar
      Vijay SRJ

      Hi while saving , are you setting the parent to the child entity .
      This line in the above post:

      comment.setBlogpost(post);

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading