Spring JPA Bidirectional ManyToMany Association Without Join Entity

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:

bidirectional many to many relationship

Source Code

Download

Leave a Reply

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