Spring JPA Hibernate Many to Many – SpringBoot + PostgreSQL

This tutorial will guide you through the steps configuring Many to Many relationship in database with Spring JPA, Spring Boot & PostgreSql.

Related articles:
How to configure Spring JPA One to One Relationship – SpringBoot
How to configure Spring JPA One to Many Relationship – SpringBoot
Spring Data Rest – How to create a RestAPIs with JPA Many-to-Many relational entities | SpringBoot + MySql + HAL Browser


I. Technology for Many-to-Many relationship tutorial

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– PostgreSQL

II. Overview

1. Many to many database design

student and subject tables have many to many relationship via student_subject table.

many to many relationship database

2. Project structure

Look at Package Explorer to see our project structure:
spring jpa many to many structure of project

3. Step to do

– Create Spring Boot project
– Config Datasource and Hibernate for JPA
– Create models that mapping with Database design.
– Create JPA repositories
– Implement client for use
– Run & Check result

III. Practices

1. Create Spring Boot project

– Open Spring Tool Suite, on main menu, choose File->New->Spring Starter Project, input project info. Then press Next.
– Press Finish, Spring Boot Project will be created successful.

Add needed dependencies:
– Spring JPA
– Postgresql
– Json

<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>org.postgresql</groupId>
		<artifactId>postgresql</artifactId>
		<scope>runtime</scope>
	</dependency>		
	
	<dependency>
		<groupId>org.json</groupId>
		<artifactId>json</artifactId>
	</dependency>

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

2. Config Datasource and Hibernate for JPA

– Open application.properties, config datasource & hibernate:


spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123

spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

3. Create models that mapping with Database design.

– Create Student Entity class:


package com.javasampleapproach.springjpa.many2many.model;

import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

import org.json.JSONArray;
import org.json.JSONObject;

@Entity
public class Student {
	
	@Id
    @GeneratedValue(strategy = GenerationType.AUTO)
	private int id;
	private String name;
	
	@ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "student_subject", joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"), 
    inverseJoinColumns = @JoinColumn(name = "subject_id", referencedColumnName = "id"))
	private Set subjects;
	
	public Student(){
		
	}
	
	public Student(String name){
		this.name = name;
	}
	
	public Student(String name, Set subjects){
		this.name = name;
		this.subjects = subjects;
	}
	
	// name
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	// subjects
	public Set getSubjects() {
		return subjects;
	}
	
	public void setSubjects(Set subjects) {
		this.subjects = subjects;
	}
	
	@Override
	public String toString(){
		String info = "";
		JSONObject jsonInfo = new JSONObject();
		jsonInfo.put("name",this.name);
		JSONArray subArray = new JSONArray();
		this.subjects.forEach(sub->{
			JSONObject subJson = new JSONObject();
			subJson.put("name", sub.getName());
			subArray.put(subJson);
		});
		jsonInfo.put("subjects", subArray);
		info = jsonInfo.toString();
		return info;
	}
}

– Create Subject Entity class:


package com.javasampleapproach.springjpa.many2many.model;

import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

import org.json.JSONArray;
import org.json.JSONObject;

@Entity
public class Subject {
	
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
	private int id;
	private String name;
	
	@ManyToMany(mappedBy = "subjects")
	private Set students;
	
    public Subject(){
    }
    
    public Subject(String name){
    	this.name = name;
    }
    
    public Subject(String name, Set students){
    	this.name = name;
    	this.students = students;
    }
	
	// name
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	// students
	public Set getStudents() {
		return students;
	}
	
	public void setStudents(Set students) {
		this.students = students;
	}
	
	@Override
	public String toString(){
		String info = "";
		JSONObject jsonInfo = new JSONObject();
		jsonInfo.put("name",this.name);
		JSONArray studentArray = new JSONArray();
		if(this.students != null && students.size() > 0){
			this.students.forEach(student->{
				JSONObject subJson = new JSONObject();
				subJson.put("name", student.getName());
				studentArray.put(subJson);
			});
		}
		jsonInfo.put("students", studentArray);
		info = jsonInfo.toString();
		return info;
	}
}

@Entity: Specifies that the class is an entity. This annotation is applied to the entity class.
@Id: Specifies the primary key of an entity.
@ManyToMany: Defines a many-valued association with many-to-many multiplicity
@JoinTable: Used in the mapping of associations. It is specified on the owning side of an association.
JoinColumn: Specifies a column for joining an entity association or element collection. If the JoinColumn annotation itself is defaulted, a single join column is assumed and the default values apply.

4. Create JPA repositories

– Create StudentRepository & SubjectRepository by extends JpaRepository.

StudentRepository :


import org.springframework.data.jpa.repository.JpaRepository;

import com.javasampleapproach.springjpa.many2many.model.Student;

public interface StudentRepository extends JpaRepository{
}

SubjectRepository:


package com.javasampleapproach.springjpa.many2many.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.javasampleapproach.springjpa.many2many.model.Subject;

public interface SubjectRepository extends JpaRepository{
}

JpaRepository: JPA specific extension of {@link org.springframework.data.repository.Repository}

5. Implement client for use

– In main class, implement code for saving and retrieve students & subjects
– Code details:


package com.javasampleapproach.springjpa.many2many;

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

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.javasampleapproach.springjpa.many2many.model.Student;
import com.javasampleapproach.springjpa.many2many.model.Subject;
import com.javasampleapproach.springjpa.many2many.repository.StudentRepository;
import com.javasampleapproach.springjpa.many2many.repository.SubjectRepository;


@SpringBootApplication
public class SpringJpaMany2ManyApplication implements CommandLineRunner{
	
	@Autowired
	StudentRepository studentRepository;
	
	@Autowired
	SubjectRepository subjectRepository;

	public static void main(String[] args) {
		SpringApplication.run(SpringJpaMany2ManyApplication.class, args);
	}

	@Transactional
	@Override
	public void run(String... arg0) throws Exception {
		Student jack = new Student("Jack");
		Student peter = new Student("Peter");
		
		Subject math = new Subject("Mathematics");
		Subject computer = new Subject("Compter");
		
		/*subjectRepository.save(math);
		subjectRepository.save(computer);*/
		
		Set subjects = new HashSet();
		subjects.add(math);
		subjects.add(computer);
		
		jack.setSubjects(subjects);
		peter.setSubjects(subjects);
		
		studentRepository.save(jack);
		studentRepository.save(peter);
		
		
		Set students = new HashSet();
		students.add(jack);
		students.add(peter);
		
		math.setStudents(students);
		computer.setStudents(students);
		
		subjectRepository.save(math);
		subjectRepository.save(computer);
		
		List studentLst = studentRepository.findAll();
		List subLst = subjectRepository.findAll();
		
		System.out.println(studentLst.size());
		System.out.println(subLst.size());
		
		
		System.out.println("===================Students info:==================");
		studentLst.forEach(student->System.out.println(student.toString()));
		
		System.out.println("===================Students info:==================");
		subLst.forEach(subject->System.out.println(subject.toString()));
	}
}

6. Run & Check result

– Build project with maven: mvn clean install
– Run with command: mvn spring-boot:run

Results:
– 3 tables is created automatically on postgresql DB: student, subject, student_subject

spring jpa many to many relationship tables

Logs:
Create and Drop tables


Hibernate: alter table student_subject drop constraint FK5cvx0kd792xhvd99s3bsbygfq
Hibernate: alter table student_subject drop constraint FKnhw926s5os3ei5wqfaq94j0mh
Hibernate: drop table if exists student cascade
Hibernate: drop table if exists student_subject cascade
Hibernate: drop table if exists subject cascade
Hibernate: drop sequence hibernate_sequence
Hibernate: create sequence hibernate_sequence start 1 increment 1
Hibernate: create table student (id int4 not null, name varchar(255), primary key (id))
Hibernate: create table student_subject (student_id int4 not null, subject_id int4 not null, primary key (student_id, subject_id))
Hibernate: create table subject (id int4 not null, name varchar(255), primary key (id))
Hibernate: alter table student_subject add constraint FK5cvx0kd792xhvd99s3bsbygfq foreign key (subject_id) references subject
Hibernate: alter table student_subject add constraint FKnhw926s5os3ei5wqfaq94j0mh foreign key (student_id) references student

– Insert:


Hibernate: insert into student (name, id) values (?, ?)
Hibernate: insert into subject (name, id) values (?, ?)
Hibernate: insert into subject (name, id) values (?, ?)
Hibernate: insert into student (name, id) values (?, ?)
Hibernate: insert into student_subject (student_id, subject_id) values (?, ?)
Hibernate: insert into student_subject (student_id, subject_id) values (?, ?)
Hibernate: insert into student_subject (student_id, subject_id) values (?, ?)
Hibernate: insert into student_subject (student_id, subject_id) values (?, ?)
Hibernate: select student0_.id as id1_0_, student0_.name as name2_0_ from student student0_
Hibernate: select subject0_.id as id1_2_, subject0_.name as name2_2_ from subject subject0_

– Info students & subjects:


===================Students info:==================
{"subjects":[{"name":"Compter"},{"name":"Mathematics"}],"name":"Jack"}
{"subjects":[{"name":"Compter"},{"name":"Mathematics"}],"name":"Peter"}
===================Students info:==================
{"name":"Compter","students":[{"name":"Jack"},{"name":"Peter"}]}
{"name":"Mathematics","students":[{"name":"Jack"},{"name":"Peter"}]}

IV. Sourcecode

SpringJpaMany2Many

11 thoughts on “Spring JPA Hibernate Many to Many – SpringBoot + PostgreSQL”

  1. what if in real input database from multiple jsp input form, rather than directly from queries, are the ORM still work with different way ? need some sample

    1. Hi Jeff,

      For official development & architecture, we need submit the form to webservice. After handling the JSP form by MVC Controller,
      We use the approach in the tutorial for submit data to Database with JPA

      About JPA, it wrap at high level of ORM, so you don’t need to handle or focus on how to ORM framework working.

      How to submit form from JSP file to web controller, you can refer at some ralated posts:

      1. How to integrate JQuery Ajax POST/GET & Spring MVC | Spring Boot
      https://ozenero.com/java-integration/integrate-jquery-ajax-post-get-spring-boot-web-service
      2. Spring Form Submission to PostgreSQL – Spring Boot
      https://ozenero.com/spring-framework/spring-boot/spring-form-submission-postgresql-spring-boot
      3. Spring Web MVC – Spring Form Submission | Spring Boot
      https://ozenero.com/spring-framework/spring-mvc/spring-web-mvc-form-submission-spring-boot
      4. How to configure AngularJs with Spring MVC | SpringBoot
      https://ozenero.com/spring-framework/spring-boot/configure-angularjs-springboot
      5. How to integrate Http Angularjs with Spring MVC | Spring Boot
      https://ozenero.com/java-integration/http-angularjs-spring-boot

      Regards,

  2. Any idea why this @Transactional annotation is working only on run method?

    If i move the code inside run method into 2 methods, 1 to save data and another to get & print data, and mark those two methods with @Transactional annotation and remove @Transactional from run method, then it gives following error. I had a similar project and there, the save works fine but the print methods fails with lazyInitializationFailed .. no session (i dont want Eager loading)

    java.lang.IllegalStateException: Failed to execute CommandLineRunner

    Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.javasampleapproach.springjpa.many2many.model.Subject; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.javasampleapproach.springjpa.many2many.model.Subject

    Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.javasampleapproach.springjpa.many2many.model.Subject

    1. Hi Nhat,

      If you have id of a student, you can retrieve the student object with statement:

      Student student = studentRepository.findOne(id)
      

      Then get size from Set of Subjects for that Student:

      int amountOfSubjects = student.getSubjects().size;
      

      Hope it helpful for you!

  3. Hi, Admin, thank you for the awesome tutorial?. Just one question, though: How can I explicitly create my own RestController and expose it as a service then consume its methods like listAllStudents etc in an external tool like postman for example? In a many-to-many relationship like this one?
    Thanks.

    1. Hi Sydney Molobela,

      You can do it!

      For creating a RestController, you can refer how to expose RestAPIs with SpringBoot via post:
      Spring Framework 4.3 New Feature RequestMapping: @GetMapping, @PostMapping, @PutMapping, @DeleteMapping

      Note: for resolving resolve infinity loop problem, you can use tutorial: Integrate Spring RestAPIs with @JsonView
      More details at:

      How to use @JsonView to serialize/de-serialize and customize JSON format from Java Object.
      How to resolve Json Infinite Recursion problems when working with Jackson

      Regards,
      JSA

  4. Hi sorry for this question, but how can affect list of subjects to one user: what i mean how to add for one user many subjects and save it in the student_subject?
    Thanks in advance.

  5. Thanks a lot for this helpful article. Do I want to know how to get all subjects by student id? Thanks in advance.

Leave a Reply

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