In this article, you’ll learn how to map a one-to-many database relationship at the object level using JPA and Hibernate.

Consider the following two tables - posts and comments of a Blog database schema exhibiting a one-to-many relationship between each other -

JPA / Hibernate One to Many Mapping Example Table Structure

We’ll create a project from scratch and learn how to go about implementing such one-to-many relationship at the object level using JPA and hibernate.

Creating the Project

If you have Spring Boot CLI installed, you can type the following command in your terminal to generate the project -

spring init -n=jpa-one-to-many-demo -d=web,jpa,mysql --package-name=com.example.jpa jpa-one-to-many-demo

Alternatively, You can generate the project from Spring Initializr web tool by following the instructions below -

  1. Go to http://start.spring.io
  2. Click Switch to full version link to see all the options
  3. Enter Artifact as “jpa-one-to-many-demo”
  4. Change Package Name to “com.example.jpa”
  5. Select Web, JPA and Mysql dependencies.
  6. Click Generate Project to download the project.

Following is the directory structure of the project for your reference -

JPA, Hibernate, Spring Boot One to Many Mapping example Directory Structure

Your bootstrapped project won’t have model and repository packages, and all the classes inside these packages at this point. We’ll create them shortly.

Configuring the Database and Logging

Since we’re using MySQL as our database, we need to configure the database URL, username, and password so that Spring can establish a connection with the database on startup. Open src/main/resources/application.properties file and add the following properties to it -

# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_one_to_many_demo?useSSL=false
spring.datasource.username=root
spring.datasource.password=root

# Hibernate

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE

Don’t forget to change the spring.datasource.username and spring.datasource.password as per your MySQL installation. Also, create a database named jpa_one_to_many_demo in MySQL before proceeding to the next section.

You don’t need to create any tables. The tables will automatically be created by hibernate from the Post and Comment entities that we will define shortly. This is made possible by the property spring.jpa.hibernate.ddl-auto = update.

We have also specified the log levels for hibernate so that we can debug all the SQL statements and learn what hibernate does under the hood.

Defining the Domain Models

Let’s now define the two domain models of our application - Post and Comment. Create a package named model inside com.example.jpa package and then create the following classes inside the model package -

1. Post model

package com.example.jpa.model;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.*;

@Entity
@Table(name = "posts")
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Size(max = 100)
    @Column(unique = true)
    private String title;

    @NotNull
    @Size(max = 250)
    private String description;

    @NotNull
    @Lob
    private String content;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "posted_at")
    private Date postedAt = new Date();

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "last_updated_at")
    private Date lastUpdatedAt = new Date();

    @OneToMany(cascade = CascadeType.ALL,
            fetch = FetchType.LAZY,
            mappedBy = "post")
    private Set<Comment> comments = new HashSet<>();

    public Post() {

    }

    public Post(String title, String description, String content) {
        this.title = title;
        this.description = description;
        this.content = content;
    }

    // Getters and Setters (Omitted for brevity)
}

2. Comment model

package com.example.jpa.model;

import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "comments")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Lob
    private String text;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id", nullable = false)
    private Post post;

    public Comment() {

    }

    public Comment(String text) {
        this.text = text;
    }

    // Getters and Setters (Omitted for brevity)
}

We use @OneToMany and @ManyToOne annotations to declare a bi-directional one-to-many relationship between two entities.

We have annotated the comments field in the Post entity with @OneToMany annotation to declare that the Post entity has a one-to-many association with the Comment entity.

Similarly, We have annotated the post field in the Comment entity with @ManyToOne annotation and defined a @JoinColumn which is used as the foreign key column in the Comment entity.

We use the mappedBy attribute in the Post entity to declare that it is not responsible for the relationship and hibernate should look for a field named post in the Comment entity to find the configuration for the JoinColumn/ForeignKey Column.

Defining the Repositories

Next, We’ll define the repositories for accessing the data from the database. Create a new package called repository inside com.example.jpa package and add the following interfaces inside the repository package -

1. PostRepository

package com.example.jpa.repository;

import com.example.jpa.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {

}

2. CommentRepository

package com.example.jpa.repository;

import com.example.jpa.model.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {

}

Testing the one-to-many relationship

Finally, Let’s write some code to test our one-to-many association setup. Open the main class JpaOneToManyDemoApplication.java and replace it with the following code -

package com.example.jpa;

import com.example.jpa.model.Comment;
import com.example.jpa.model.Post;
import com.example.jpa.repository.CommentRepository;
import com.example.jpa.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaOneToManyDemoApplication implements CommandLineRunner {

    @Autowired
    private PostRepository postRepository;

    @Autowired
    private CommentRepository commentRepository;

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

    @Override
    public void run(String... args) throws Exception {
        // Cleanup Database tables
        commentRepository.deleteAllInBatch();
        postRepository.deleteAllInBatch();

        // ======================================

        Post post = new Post("Hibernate One-To-Many Mapping Example",
                "Learn how to use one to many mapping in hibernate",
                "Entire Post Content with sample code");

        Comment comment1 = new Comment("Great Post!");
        comment1.setPost(post);

        Comment comment2 = new Comment("Really helpful Post. Thanks a lot!");
        comment2.setPost(post);

        post.getComments().add(comment1);
        post.getComments().add(comment2);

        postRepository.save(post);

        // ======================================

    }
}

The run() method is executed once the application starts. In the run() method, we first clean up the tables and then create a Post as well as two Comments and insert them into the database.

Running the Application

You can run the application by typing the following command in the terminal -

mvn spring-boot:run

Check the logs to find out what SQL statements hibernate executes for the operations that we have performed in the run() method -

org.hibernate.SQL : delete from comments
org.hibernate.SQL : delete from posts
org.hibernate.SQL : insert into posts (content, description, last_updated_at, posted_at, title) values (?, ?, ?, ?, ?)
org.hibernate.SQL : insert into comments (post_id, text) values (?, ?)
org.hibernate.SQL : insert into comments (post_id, text) values (?, ?)

Conclusion

That’s all folks! In this article, You learned how to map a one-to-many database relationship using JPA and Hibernate.

You can find the code for the sample project that we built in this article in my jpa-hibernate-tutorials repository on github.

You might also be interested in checking out the following articles on JPA/Hibernate Mapping -

JPA / Hibernate One to One mapping Example with Spring Boot

JPA / Hibernate Many to Many mapping Example with Spring Boot

Thanks for reading. I hope you found the content useful. Consider subscribing to my newsletter if you don’t wanna miss future articles from The CalliCoder Blog.