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
– Spring Boot GraphQL MySQL CRUD APIs example
Contents
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.
2. Project structure
Look at Package Explorer to see our project structure:
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<Subject> subjects; public Student(){ } public Student(String name){ this.name = name; } public Student(String name, Set<Subject> subjects){ this.name = name; this.subjects = subjects; } // name public String getName() { return name; } public void setName(String name) { this.name = name; } // subjects public Set<Subject> getSubjects() { return subjects; } public void setSubjects(Set<Subject> 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<Student> students; public Subject(){ } public Subject(String name){ this.name = name; } public Subject(String name, Set<Student> students){ this.name = name; this.students = students; } // name public String getName() { return name; } public void setName(String name) { this.name = name; } // students public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> 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<Student, Integer>{ } |
– 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<Subject, Integer>{ } |
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<Subject> subjects = new HashSet<Subject>(); subjects.add(math); subjects.add(computer); jack.setSubjects(subjects); peter.setSubjects(subjects); studentRepository.save(jack); studentRepository.save(peter); Set<Student> students = new HashSet<Student>(); students.add(jack); students.add(peter); math.setStudents(students); computer.setStudents(students); subjectRepository.save(math); subjectRepository.save(computer); List<Student> studentLst = studentRepository.findAll(); List<Subject> 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
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
Last updated on November 18, 2019.
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
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://grokonez.com/java-integration/integrate-jquery-ajax-post-get-spring-boot-web-service
2. Spring Form Submission to PostgreSQL – Spring Boot
https://grokonez.com/spring-framework/spring-boot/spring-form-submission-postgresql-spring-boot
3. Spring Web MVC – Spring Form Submission | Spring Boot
https://grokonez.com/spring-framework/spring-mvc/spring-web-mvc-form-submission-spring-boot
4. How to configure AngularJs with Spring MVC | SpringBoot
https://grokonez.com/spring-framework/spring-boot/configure-angularjs-springboot
5. How to integrate Http Angularjs with Spring MVC | Spring Boot
https://grokonez.com/java-integration/http-angularjs-spring-boot
Regards,
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
Because the Lazy loading need a Session so @Transaction is a solution for this case.
You can see more about: Hibernate Lazy & Eager Fetch Type at: https://grokonez.com/hibernate/use-hibernate-lazy-fetch-eager-fetch-type-spring-boot-mysql
How to find the amount of subject that a student have ?
Hi Nhat,
If you have
id
of a student, you can retrieve thestudent
object with statement:Then get size from
Set
ofSubjects
for that Student:Hope it helpful for you!
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.
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
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.
Thanks a lot for this helpful article. Do I want to know how to get all subjects by student id? Thanks in advance.
how to delete student record using JPA(using many to many relationships, if we use this example (https://grokonez.com/spring-framework/spring-boot/spring-jpa-many-to-many))