In the previous article, I wrote about how to define and use a one-to-one relationship with Spring Data JPA and MySQL in a Spring Boot application.

In this article, you'll learn how to map a one-to-many database relationship using Spring Data JPA in a Spring Boot and MySQL application.

Dependencies

We need both spring-data-starter-data-jpa and mysql-connector-java dependencies to use Spring Data JPA with the MySQL database in Spring Boot.

If you are using Gradle, add the following dependencies to your build.gradle file:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java'

If you are using Maven, include the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Want to create a new Spring Boot project from scratch? Just use Spring Initializr web tool to bootstrap a new application with the above dependencies.

Configure MySQL Database

Spring Boot auto-configures the DataSource bean for in-memory databases like H2 database, HSQLDB, and Apache Derby. Since we are using MySQL, we need to explicitly define the database connection properties in a properties file.

Open the application.properties file in your favorite editor and add the following properties:

# MySQL connection properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=mypass
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?createDatabaseIfNotExist=true&useSSL=false

# Log JPA queries
# Comment this in production
spring.jpa.show-sql=true

# Drop and create new tables (create, create-drop, validate, update)
# Only for testing purpose - comment this in production
spring.jpa.hibernate.ddl-auto=create

# Hibernate SQL dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

Spring Boot will automatically configure a DataSource bean after reading the above properties. Don't forget to change the spring.datasource.username and spring.datasource.password properties to match your MySQL database installation.

The hibernate property spring.jpa.hibernate.ddl-auto = create will automatically create database tables based on the entity classes in your application on application startup.

The createDatabaseIfNotExist=true configuration property, we have included in spring.datasource.url, makes sure that the database schema is automatically created if it doesn't already exist.

One-To-Many Relationship

A one-to-many relationship refers to the relationship between two entities/tables A and B in which one element/row of A may only be linked to many elements/rows of B, but a member of B is linked to only one element/row of A.

For instance, think of A as a book, and B as pages. A book can have many pages but a page can only exist in one book, forming a one-to-many relationship. The opposite of one-to-many is many-to-one relationship.

Let us model the above relationship in the database by creating two tables, one for the books and another for the pages, as shown below in an Entity-Relationship (ER) diagram:

Spring Data JPA One To Many Mapping

The one-to-many relationship is defined by the foreign key book_id in the pages table.

Create Entities

The next step is to create the Book and Page entities and define the one-to-many relationship mapping, as shown below:

Book.java

package com.attacomsian.jpa.one2many.domains;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;

@Entity
@Table(name = "books")
public class Book implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;
    @Column(unique = true)
    private String isbn;

    @OneToMany(mappedBy = "book", fetch = FetchType.LAZY,
            cascade = CascadeType.ALL)
    private Set<Page> pages;

    public Book() {
    }

    public Book(String title, String author, String isbn) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }

    // getters and setters, equals(), toString() .... (omitted for brevity)
}

Page.java

package com.attacomsian.jpa.one2many.domains;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "pages")
public class Page implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private int number;
    private String content;
    private String chapter;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "book_id", nullable = false)
    private Book book;

    public Page() {
    }

    public Page(int number, String content, String chapter, Book book) {
        this.number = number;
        this.content = content;
        this.chapter = chapter;
        this.book = book;
    }

    // getters and setters, equals(), toString() .... (omitted for brevity)
}

Both Book and Page classes are annotated with the Entity annotation to indicate that they are JPA entities.

The @Table annotation is used to specify the name of the database table that should be mapped to this entity.

The id attributes are annotated with both @Id and @GeneratedValue annotations. The former annotation indicates that they are the primary keys of the entities. The latter annotation defines the primary key generation strategy. In the above case, we have declared that the primary key should be an AUTO INCREMENT field.

@OneToMany Annotation

A one-to-many relationship between two entities is defined by using the @OneToMany annotation in Spring Data JPA. It declares the mappedBy element to indicate the entity that owns the bidirectional relationship. Usually, the child entity is one that owns the relationship and the parent entity contains the @OneToMany annotation.

@ManyToOne Annotation

The @ManyToOne annotation is used to define a many-to-one relationship between two entities in Spring Data JPA. The child entity, that has the join column, is called the owner of the relationship defined using the @ManyToOne annotation.

@JoinColumn Annotation

The @JoinColumn annotation is used to specify the foreign key column in the owner of the relationship. The inverse-side of the relationship sets the mappedBy attribute to indicate that the relationship is owned by the other entity.

Create Repositories

Let us now define the repository interfaces to store and access the data from the database. We'll be extending our repositories from Spring Data JPA's CrudRepository interface that provides methods for generic CRUD operations.

BookRepository.java

package com.attacomsian.jpa.one2many.repositories;

import com.attacomsian.jpa.one2many.domains.Book;
import org.springframework.data.repository.CrudRepository;

public interface BookRepository extends CrudRepository<Book, Long> {

    Book findByIsbn(String isbn);
}

PageRepository.java

package com.attacomsian.jpa.one2many.repositories;

import com.attacomsian.jpa.one2many.domains.Book;
import com.attacomsian.jpa.one2many.domains.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface PageRepository extends CrudRepository<Page, Long> {

    List<Page> findByBook(Book book, Sort sort);
}

In the above repositories, we also defined some derived query methods like findByIsbn() to fetch a book by its ISBN number.

That's it. You have successfully defined a one-to-many relationship mapping in Spring Data JPA. You don't need to implement the above interfaces thanks to Spring Data JPA.

Create an Application Class

Let us now create the main application class for the Spring Boot console application to test our one-to-many relationship mapping:

Application.java

package com.attacomsian.jpa;

import com.attacomsian.jpa.one2many.domains.Book;
import com.attacomsian.jpa.one2many.domains.Page;
import com.attacomsian.jpa.one2many.repositories.BookRepository;
import com.attacomsian.jpa.one2many.repositories.PageRepository;
import com.attacomsian.jpa.one2one.domains.Address;
import com.attacomsian.jpa.one2one.domains.User;
import com.attacomsian.jpa.one2one.repositories.AddressRepository;
import com.attacomsian.jpa.one2one.repositories.UserRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

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

    @Bean
    public CommandLineRunner mappingDemo(BookRepository bookRepository,
                                         PageRepository pageRepository) {
        return args -> {

            // create a new book
            Book book = new Book("Java 101", "John Doe", "123456");

            // save the book
            bookRepository.save(book);

            // create and save new pages
            pageRepository.save(new Page(1, "Introduction contents", "Introduction", book));
            pageRepository.save(new Page(65, "Java 8 contents", "Java 8", book));
            pageRepository.save(new Page(95, "Concurrency contents", "Concurrency", book));
        };
    }
}

Run the Application

Next, run the application to see the output. If you are using Gradle, execute the following command to start the application:

$ ./gradlew bootRun

For Maven, type the following command to launch the application:

$ ./mvnw spring-boot:run

Once the application is started, you should see the following lines printed on the console:

Hibernate: drop table if exists books
Hibernate: drop table if exists pages
Hibernate: create table books (id bigint not null auto_increment, author varchar(255), isbn varchar(255), title varchar(255), primary key (id)) engine=InnoDB
Hibernate: create table pages (id bigint not null auto_increment, chapter varchar(255), content varchar(255), number integer not null, book_id bigint not null, primary key (id)) engine=InnoDB
Hibernate: insert into books (author, isbn, title) values (?, ?, ?)
Hibernate: insert into pages (book_id, chapter, content, number) values (?, ?, ?, ?)
Hibernate: insert into pages (book_id, chapter, content, number) values (?, ?, ?, ?)
Hibernate: insert into pages (book_id, chapter, content, number) values (?, ?, ?, ?)
...

Source Code: Download the complete source code from GitHub available under MIT license.

Conclusion

That's all folks. In this article, you've learned how to map and use a one-to-many relationship in Spring Data JPA and Hibernate.

Don't forget to join the mailing list if you want to be the first to know when new tutorials are available.

Further Reading

To learn about Spring Data JPA, check out the following articles:

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)

Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.