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 purposes - 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 using the above properties. Do not 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, which we have included in spring.datasource.url
, automatically creates the database schema 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 a 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:
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 indicates that they are the primary keys of the entities. The latter 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 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 owns the relationship, and the parent entity contains the @OneToMany
annotation.
@ManyToOne
Annotation
The @ManyToOne
annotation defines 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 relationship owner. 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. In this article, you 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:
- Getting Started with Spring Data JPA
- Spring Data JPA with H2 DataBase and Spring Boot
- Accessing Data with Spring Data JPA and MySQL
- Derived Query Methods in Spring Data JPA
- Spring Data JPA Custom Queries using @Query Annotation
- How to Use Spring Data JPA Named Queries
- Sorting Query Results in Spring Data JPA
- Pagination with Spring Data JPA
- Spring Data JPA One To One Relationship Mapping Example
- Spring Data JPA Many To Many Relationship Mapping Example
✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.