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.