Spring Data JPA Bidirectional Many-To-One/One-To-Many Relationship

Introduction

In bidirectional association, you will have navigations in both direction, i.e, both side of the association will have the reference to the other side. The both side of the association will implement one of the collection interfaces, if it has the reference to the other entity.

In many-to-one/one-to-many relationship, multiple source objects can have relationship with same target object or same target object will have multiple source objects and from both direction the navigation is possible. Let’s consider CD and Artist. So multiple CDs can be written by same Artist or same Artist can have multiple CDs.

So I will create two tables CD and Artist in the database and I will show you how many-to-one/one-to-many relationship works suing Spring Boot Data JPA API in Hibernate.

So multiple CDs can be written by same Artist or the same Artist can have multiple CDs.

Prerequisites

Java 1.8+ (11 – 16), Maven 3.8.2, Spring Boot 2.5.6

Project Setup

Create a maven based project in your favorite IDE or tool. The name of the project is spring-jpa-bidirectional-manytoone-onetomany. You can use the following pom.xml file for your maven based project.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>spring-jpa-bidirectional-manytoone-onetomany</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>16</maven.compiler.source>
		<maven.compiler.target>16</maven.compiler.target>
	</properties>


	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.6</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Entity Class – Artist

The following entity class Artist has @OneToMany relationship with Cd entity class. This means a single Artist can have many Cds.

Also notice I am looping through a set of Cds and setting Artist explicitly to each Cd. If you do not do so, then you will get the following exception:

Not null property references a null or transient value. The above exception occurs because optional = false in @ManyToOne mapping.

package com.roytuts.spring.jpa.bidirectional.manytoone.onetomany.entity;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Table
@Entity
public class Artist {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int artistId;

	@Column
	private String artistName;

	@OneToMany(mappedBy = "artist", cascade = CascadeType.ALL)
	private Set<Cd> cds = new HashSet<>();

	public int getArtistId() {
		return artistId;
	}

	public void setArtistId(int artistId) {
		this.artistId = artistId;
	}

	public String getArtistName() {
		return artistName;
	}

	public void setArtistName(String artistName) {
		this.artistName = artistName;
	}

	public Set<Cd> getCds() {
		return cds;
	}

	public void setCds(Set<Cd> cds) {
		this.cds = cds;

		for (Cd cd : cds) {
			cd.setArtist(this);
		}
	}

}

Entity Class – Cd

The following entity class Cd has @ManyToOne relationship with Artist entity class. This means the many Cds can have single Artist.

package com.roytuts.spring.jpa.bidirectional.manytoone.onetomany.entity;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Table
@Entity
public class Cd {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int cdId;

	@Column
	private String cdTitle;

	@JoinColumn(name = "artistId")
	@ManyToOne(cascade = CascadeType.ALL, optional = false)
	private Artist artist;

	public int getCdId() {
		return cdId;
	}

	public void setCdId(int cdId) {
		this.cdId = cdId;
	}

	public String getCdTitle() {
		return cdTitle;
	}

	public void setCdTitle(String cdTitle) {
		this.cdTitle = cdTitle;
	}

	public Artist getArtist() {
		return artist;
	}

	public void setArtist(Artist artist) {
		this.artist = artist;
	}

}

Repository – Artist

The ArtistRepository extends JpaRepository to get the benefits of built-in APIs from Spring Data JPA.

public interface ArtistRepository extends JpaRepository<Artist, Integer> {

}

Repository – Cd

The CdRepository extends JpaRepository to get the benefits of built-in APIs from Spring Data JPA. For example, Spring framework provides useful methods for performing basic CRUD operations out of the box.

public interface CdRepository extends JpaRepository<Cd, Integer> {

}

Service Class

The following service class defines two methods through which you can save values for the One To Many and Many To One bidirectional relationship.

@Service
public class ManyToOneOneToManyService {

	@Autowired
	private CdRepository cdRepository;

	@Autowired
	private ArtistRepository artistRepository;

	public void saveCdArtistList(List<Cd> cds) {
		cdRepository.saveAll(cds);
	}

	public void saveArtistCdList(Artist artist) {
		artistRepository.save(artist);
	}

}

application.properties

spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/roytuts
spring.datasource.username=root
spring.datasource.password=root
 
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
 
logging.level.org.hibernate.type.descriptor.sql=trace
 
spring.jpa.hibernate.ddl-auto = create
#spring.jpa.hibernate.ddl-auto = none

I have set the following properties so that Hibernate creates the required tables. If you want that your entity classes should not create table in database then change the value from create to none.

spring.jpa.hibernate.ddl-auto=create

I am logging SQL statement and formatting them for better reading in the console using the following key/value pairs:

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.type.descriptor.sql=trace

Testing Bidirectional One-to-many/Many-to-one

The following output is produced during application startup.

Hibernate:
    
    drop table if exists artist
Hibernate:
    
    drop table if exists cd
Hibernate:
    
    create table artist (
       artist_id integer not null auto_increment,
        artist_name varchar(255),
        primary key (artist_id)
    ) engine=InnoDB
Hibernate:
    
    create table cd (
       cd_id integer not null auto_increment,
        cd_title varchar(255),
        artist_id integer not null,
        primary key (cd_id)
    ) engine=InnoDB
Hibernate:
    
    alter table cd
       add constraint FK9h0ltj552jugnhsskumg52wtm
       foreign key (artist_id)
       references artist (artist_id)
---------------------------------------------------
Many To One Testing
---------------------------------------------------
Hibernate:
    insert
    into
        artist
        (artist_name)
    values
        (?)
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Soumitra]
Hibernate:
    insert
    into
        cd
        (artist_id, cd_title)
    values
        (?, ?)
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [1]
o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Java]
Hibernate:
    insert
    into
        cd
        (artist_id, cd_title)
    values
        (?, ?)
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [1]
o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [PHP]
---------------------------------------------------
One To Many Testing
---------------------------------------------------
Hibernate:
    insert
    into
        artist
        (artist_name)
    values
        (?)
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Soumitra]
Hibernate:
    insert
    into
        cd
        (artist_id, cd_title)
    values
        (?, ?)
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [2]
o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Java]
Hibernate:
    insert
    into
        cd
        (artist_id, cd_title)
    values
        (?, ?)
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [2]
o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [PHP]

In the above output, you can clearly see that the required tables are created in the database and values are inserted into the tables. The cd and artist tables will have the following data after insertion.

You can also verify the database table whether data have been inserted or not.

spring data jpa one-to-many and many-to-one bidirectional relationship

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *