Introduction
Here I am creating an example on how bidirectional many-to-many association without join entity works in entity relationships. I will use SPring Data JPA framework to build this example.
In bidirectional association, it will have the navigation in both directions, 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 many relationship, one multiple objects can have relationship with multiple target objects. Let’s consider CD and Artist. So multiple CDs can be written by multiple Artists or multiple Artists can write multiple CDs. So, this application will create three tables cd, cd_artist and artist in the database.
In this example, I am not going to create tables manually, but I will use the property spring.jpa.hibernate.ddl-auto = create
, in the application.properties file, which will create the required tables. If you do not want to create tables from entity class then you can change its value to none
instead of create
.
Prerequisites
Java 1.8+ (11 -16), Maven 3.6.3 – 3.8.2, Spring Boot 2.5.6, MySQL 8.0.26
Project Setup
Your maven based project may have the following required dependencies in the following pom.xml file:
<?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-manytomany-without-join-entity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<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>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
The class path file application.properties under folder src/main/resources defines the datasource for the MySQL dataabse.
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 am also logging the SQL statements executed in the server with readable format:
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.type.descriptor.sql=trace
To create tables in database I have used the following property:
spring.jpa.hibernate.ddl-auto = create
Entity Class – Artist
The following entity class might have one or more cds written by them:
@Table
@Entity
public class Artist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int artistId;
@Column
private String artistName;
@ManyToMany(mappedBy = "artists", cascade = CascadeType.ALL)
private Set<Cd> cds;
//getters and setters
}
The table name (artist) will be same as the entity class with initial small letter of the class name. If you want to create different name then specify the name in the @Table
annotation. If you want to create different column names then you can also specify the column name in the @Column
annotation.
Entity Class – Cd
The following entity class creates a table cd in the database.
@Table
@Entity
public class Cd {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int cdId;
@Column
private String cdTitle;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "artist_cd", joinColumns = @JoinColumn(name = "cd_id"), inverseJoinColumns = @JoinColumn(name = "artist_id"))
private Set<Artist> artists;
//getters and setters
}
The above entity class will also create artist_cd as a third table because it has to maintain many to many relationships between artist and cd tables.
Repository – ArtistRepository
The repository interface extends the JpaRepository to inherit the built-in methods provided by Spring Data JPA without creating additional query. JpaRepository provides useful methods for building applications with basic CRUD (Create, Read, Update and Delete) operations.
public interface ArtistRepository extends JpaRepository<Artist, Integer> {
}
Repository – CdRepository
Simialrly I have created CdRepository.
public interface CdRepository extends JpaRepository<Cd, Integer> {
}
Service Class
The service class where business logic for an application is written. Here it is a simple logic to save the artist and cd information.
@Service
public class ManyToManyService {
@Autowired
private CdRepository cdRepository;
@Autowired
private ArtistRepository artistRepository;
public void saveCd(Cd cd) {
cdRepository.save(cd);
}
public void saveArtist(Artist artist) {
artistRepository.save(artist);
}
}
Spring Boot Main Class
A class with main method having annotation @SpringBootAPplication
will start the application right away.
@SpringBootApplication
@EntityScan("com.roytuts.spring.jpa.bidirectional.manytomany.without.join.entity.entity")
@EnableJpaRepositories("com.roytuts.spring.jpa.bidirectional.manytomany.without.join.entity.repository")
public class BidirectionalManyToManyWithoutJoinEntityApp implements CommandLineRunner {
@Autowired
private ManyToManyService manyToManyService;
public static void main(String[] args) {
SpringApplication.run(BidirectionalManyToManyWithoutJoinEntityApp.class, args);
}
@Override
public void run(String... args) throws Exception {
Artist a1 = new Artist();
a1.setArtistName("abc");
Artist a2 = new Artist();
a2.setArtistName("klm");
Artist a3 = new Artist();
a3.setArtistName("xyz");
Set<Artist> artists = new HashSet<>();
artists.add(a1);
artists.add(a2);
Cd cd1 = new Cd();
cd1.setCdTitle("Java");
Cd cd2 = new Cd();
cd2.setCdTitle("PHP");
Cd cd3 = new Cd();
cd3.setCdTitle("MySQL");
Set<Cd> cds = new HashSet<>();
cds.add(cd1);
cds.add(cd2);
a3.setCds(cds);
cd3.setArtists(artists);
manyToManyService.saveArtist(a3);
manyToManyService.saveCd(cd3);
}
}
Testing ManyToMany Relationship
Executing the above main class will prodcue the following output in the log:
Hibernate:
drop table if exists artist
Hibernate:
drop table if exists artist_cd
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 artist_cd (
cd_id integer not null,
artist_id integer not null,
primary key (cd_id, artist_id)
) engine=InnoDB
Hibernate:
create table cd (
cd_id integer not null auto_increment,
cd_title varchar(255),
primary key (cd_id)
) engine=InnoDB
Hibernate:
alter table artist_cd
add constraint FK3d5r8pjfg8hggi0uaoha97g25
foreign key (artist_id)
references artist (artist_id)
Hibernate:
alter table artist_cd
add constraint FKn6bak04ho6f6jrgmskj766xl9
foreign key (cd_id)
references cd (cd_id)
Hibernate:
insert
into
artist
(artist_name)
values
(?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [xyz]
Hibernate:
insert
into
cd
(cd_title)
values
(?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [PHP]
Hibernate:
insert
into
cd
(cd_title)
values
(?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Java]
Hibernate:
insert
into
cd
(cd_title)
values
(?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [MySQL]
Hibernate:
insert
into
artist
(artist_name)
values
(?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [klm]
Hibernate:
insert
into
artist
(artist_name)
values
(?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [abc]
Hibernate:
insert
into
artist_cd
(cd_id, artist_id)
values
(?, ?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [INTEGER] - [3]
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [2]
Hibernate:
insert
into
artist_cd
(cd_id, artist_id)
values
(?, ?)
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [INTEGER] - [3]
[ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [3]
So. in the above output it is clear that the application has created required three tables in the database and stored the multiple artists for a single cd or multiple cds for a single artist.
The database tables will contain the following data: