In this article, we will learn how to implement One-To-Many Relationship mapping with Spring Boot JPA. Let’s dive into it.

Spring JPA One to Many Relationship

One-to-many relationship mapping means that one row in parent is associated with multiple rows in child entity or table. For instance, let’s take Person entity and CreditCard entity then a Person can have multiple credit card. This establishes one-to-many relationship. One-To-Many Relationship exist when multiple records in one table have an association with other tables. Let’s take an example:

One-To-Many Relationship Spring JPA
OneToMany – Page-Post

As you can see in the picture, i have taken a general relationship representation for post and page. A page can have multiple post linked to it. For this article, we will be taking Page and Post entity, a page can have multiple post.

@OneToMany and @ManyToOne annotation is used to link two entities which we will be discussing more about later in the course.

Database Relationship

In our example, we will be using H2 database, you can use MySQL or PostgreSQL as you like. I will walk you through stepwise for each database.There are two tables we will be dealing with post and page.

Post is the table that persists post metadata and Page entity contains metadata related to page.

Setup Spring Boot JPA Project

Let’s use Spring Starter tool to generate a project with one click. You can add the three dependency we need while generating the project: Spring Web, Spring Data JPA. I will be using Intellij. You can use any development tools you like for example Spring Tool Suite or Eclipse.

Once you download the project, your pom should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.rtech</groupId>
	<artifactId>ManyToManyRelationShip.SpringBoot.JPA</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>ManyToManyRelationShip.SpringBoot.JPA</name>
	<description>Demo project for Spring Boot JPA Many To Many Relationship</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

If you are planning to build project from scratch then, you can add the libraries manually as mentioned above.

Configure Spring Boot JPA with H2, MySQL or PostgreSQL

We need one more dependency for datasource. As i have mentioned above, you can use any datasource you like and for this part we need to add one more dependency depending on the datasource.

For MySQL

Let’s add MySQL dependency in pom.xml.

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

Along with it, we also need to update application.properties file.

spring.datasource.url= jdbc:mysql://localhost:3306/your_database_name?useSSL=false
spring.datasource.username= your_username
spring.datasource.password= your_password
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update

For PostgreSQL

To add PostgreSQL dependency in pom.xml.

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

Along with it, we also need to update application.properties file.

spring.datasource.url=jdbc:postgresql://localhost:5432/your_database_name
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql= true

For H2

To add H2 dependency in pom.xml.

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>

Along with it, we also need to update application.properties file.

spring.datasource.url=jdbc:h2:mem:your_database_name //i will be using jpadb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto= update
spring.h2.console.enabled=true
# h2-console is default path for H2
  • spring.datasource.url is used to define database url. “jdbc:h2:mem:[your_database_name]” is how you define the url for H2.
  • spring.datasource.username and spring.datasource.password are the credentials to connect to database.
  • spring.jpa.hibernate.ddl-auto is used to initialize database. It will create database tables automatically based on our defined model. You can also set this property to following values: none, validate, update, create-drop. For production, it is better to use validate as property.
  • spring.h2.console.enabled=true will tell Spring to start H2 Database administration tool. We can access this on http://localhost:8080/h2-console
  • spring.jpa.show-sql=true is to log database transaction on the console
  • spring.jpa.properties.hibernate.dialect is to configure Hibernate for JPA implementation. It’s different for MySQL and PostgreSQL.

Define Data Model class for Spring JPA One to Many relationship

In model package, we will define two classes named Post and Page. We need to define the relationship with annotation and spring jpa will automatically take care of that. I will be defining these inside package named model.

Post

package com.rtech.onetomany.model;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;

@Entity
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "name")
    private String name;
    @Column(name = "description")
    private String description;
    @Column(name = "author")
    private String author;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "page_id")
    @JsonIgnore
    Page page;

    public Post() {
    }

    public Post(String name, String description, String author) {
        this.name = name;
        this.description = description;
        this.author = author;
    }

    public Post(String name, String description, String author, Page page) {
        this.name = name;
        this.description = description;
        this.author = author;
        this.page = page;
    }

    //removed getter and setter for brevity
    //Need to add getter and setter if are using the same code.
}

Page

I am defining the class inside model package again.

package com.rtech.onetomany.model;

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

@Entity
@Table(name = "page")
public class Page {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "title")
    private String title;

    @OneToMany(mappedBy = "page", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    List<Post> posts;
    public Page(){}

    public Page(String title) {
        this.title = title;
    }

    //removed getter and setter for brevity
    //Need to add getter and setter if are using the same code.
}

@Entity

This annotation tells Spring JPA that the class is a persistent Java class and the table has to be mapped on to the database.

@Table

The annotation maps the class name to a table name in datasource. “name” property defines table name.

@Id

A table needs to have a unique key as an attribute based on which you can perform query for uniqueness. @Id annotation tells that the column is a primary key. The primary key can be generated by different ways. @GeneratedValue defines how the primary key will be generated.

@Column

Table is supposed to have different columns and this column is mapped to attributes name in Java persistent class. @Column annotation tells Spring JPA that the attribute is a column of the associated table. With name property column name can be given too.

@ManyToOne

@ManyToOne annotation is applied to the attribute that associates the relationship between two entities. This annotation has to be applied in one of the class.

@JoinColumn

@JoinColumn annotation holds primary key of owning table.

Cascade

Cascading is the relationship between two entity. Let’s say we have Person and Address as two entity. Address does not exist without Person and hence if person is deleted then Address has to be deleted.

There are different types of JPA cascade types: for example: all, persist, merge, remove, refresh etc. CascadeType.ALL propagates all operations from parent to child entity. CascadeType.PERSIST propagates save operation from a parent to a child entity. CascadeType.REMOVE removes child entity row if parent is removed. 

FetchType

In eager loading if we load the class or parent entity then all the attributes or child entity is also loaded and stored in memory but with lazy loading child entity is loaded only with an explicit call. Hibernate provides lazy loading approach by proxy implementation on the classes.

Eager loading is a design pattern which initializes data at the very first. Lazy loading is a design pattern which defers data initialization until it is required.

Define Repository Interface Spring JPA

Inside the repository package, let’s create two interface that extends JpaRepository; PageRepository and PostRepository. These repository is defined under repository package.

It provides some Generic Crud operations like save(), findById(), deleteById(), findAll() and so on. There are many such method which comes out of the box and can use without even implementing.

PostRepository

package com.rtech.onetomany.repository;

import com.rtech.onetomany.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Integer> {
}

PageRepository

package com.rtech.onetomany.repository;

import com.rtech.onetomany.model.Page;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PageRepository extends JpaRepository<Page, Integer> {
}

Define Spring Rest Controller for Spring JPA

In this section, we will be creating a simple two controller for Page and Post. These controllers is defined under controller package.

PostController

package com.rtech.onetomany.controller;


import com.rtech.onetomany.model.Post;
import com.rtech.onetomany.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@RestController
public class PostController {

    @Autowired
    PostRepository postRepository;


    @GetMapping("/post/{id}")
    public ResponseEntity<Post> getPostById(@PathVariable("id") int id) {
        Optional<Post> post = postRepository.findById(id);
        if(post.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(post.get(), HttpStatus.OK);
    }

    @PostMapping("/post")
    public ResponseEntity<Post> createPost(@RequestBody Post post) {
        Post resPost = postRepository.save(new Post(post.getName(), post.getDescription(), post.getAuthor()));
        return new ResponseEntity<>(resPost, HttpStatus.CREATED);
    }

    @DeleteMapping("/post/{id}")
    public ResponseEntity<HttpStatus> deletePostById(@PathVariable("id") int id) {
        postRepository.deleteById(id);
        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }

}

PageController

package com.rtech.onetomany.controller;

import com.rtech.onetomany.model.Page;
import com.rtech.onetomany.model.Post;
import com.rtech.onetomany.repository.PageRepository;
import com.rtech.onetomany.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

@RestController
public class PageController {

    @Autowired
    PageRepository pageRepository;

    @Autowired
    PostRepository postRepository;


    @GetMapping("/page/{id}")
    public ResponseEntity<Page> getPageById(@PathVariable("id") int id) {
        Optional<Page> page = pageRepository.findById(id);
        if(page.isEmpty()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(page.get(), HttpStatus.OK);
    }

    @PostMapping("/page")
    public ResponseEntity<Page> createPage(@RequestBody Page page) {
        Page resPage = pageRepository.save(new Page(page.getTitle()));
        return new ResponseEntity<>(resPage, HttpStatus.CREATED);
    }

    @PostMapping("/page/{id}/post")
    public ResponseEntity<Page> createPage(@PathVariable("id") int id, @RequestBody Post post) {
        Optional<Page> optPage = pageRepository.findById(id);
        if(optPage.get() == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        Page page = optPage.get();
        if(post.getId() != null) {
            Optional<Post> optPost = postRepository.findById(post.getId());
            if(optPost.get() != null) {
                post.setPage(page);
                postRepository.save(post);
            } else {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }
        } else {
            Post resPost = postRepository.save(new Post(post.getName(), post.getDescription(), post.getAuthor(), page));
        }
        return new ResponseEntity<>(page, HttpStatus.CREATED);
    }

    @DeleteMapping("/page/{id}")
    public ResponseEntity<HttpStatus> deletePageById(@PathVariable("id") int id) {
        pageRepository.deleteById(id);
        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
}

Testing of One To Many Relationship Spring Data JPA

Project structure

This is how your project will look like once you build the project.

Spring Data JPA Project structure

Postman testing

Once you run the application, it will be exposed on 8080 port and will be using postman to test the functionalities.

Postman: Create request for Post

tman: Create request for Post

Postman: Create request for Page

Postman: Create request for Page

Postman: Add Post to Page

Postman: Add Post to Page

Conclusion

In this article we built a Spring JPA project implementing one-to-many relationship with different database. Let me know if you found this article helpful through comments.