The Spring Boot JPA Many To Many Extracolumns Relationship Mapping Example shows you the process of mapping an many-to-many relationship using Spring Data JPA and Spring Boot. A ManyToMany relationship in Java is where the source object has an attribute that stores a collection of target objects and (if) those target objects had the inverse relationship back to the source object it would also be a ManyToMany relationship. Here is that two classes have a ManyToMany relationship, but the relational join table has additional data.
Other interesting posts you may like
Let’s begin:
Project structure
Our classic mvn project in this Spring Boot JPA Many To Many Extracolumns Relationship Mapping Example.
Maven dependencies
Our Spring Boot JPA Many To Many Extracolumns Relationship Mapping Example will use JPA, MySQL, so that we must add these dependencies in the pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<?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.javabycode</groupId> <artifactId>spring-boot-jpa-many-to-many-mysql-example</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Spring Boot JPA Many-To-Many Example</name> <description>Spring Boot JPA Many-To-Many Example</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
Many-To-Many Relationship
We are using a database named example_manytomany and two student and subject tables . The subject and student tables have a many-to-many relationship via a join table named student_subject. But this join table has additional data.
Here is the sql script
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
CREATE DATABASE IF NOT EXISTS `example_manytomany` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `example_manytomany`; -- -- Table structure for table `student` -- DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8; -- -- Table structure for table `subject` -- DROP TABLE IF EXISTS `subject`; CREATE TABLE `subject` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- -- Table structure for table `student_subject` -- DROP TABLE IF EXISTS `student_subject`; CREATE TABLE `student_subject` ( `student_id` int(10) unsigned NOT NULL, `subject_id` int(10) unsigned NOT NULL, `registered_date` datetime DEFAULT NULL, PRIMARY KEY (`student_id`,`subject_id`), KEY `fk_studentsubject_subject_idx` (`subject_id`), CONSTRAINT `fk_studentsubject_student` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_studentsubject_subject` FOREIGN KEY (`subject_id`) REFERENCES `subject` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
Create JPA Entities
Entity class Student and Subject are simple POJO class. Here we are using class Student and Subject with JPA @Entity annotation to map them to a database tables (these tables were created in above step).
Student entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package com.javabycode.jpa.model; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity public class Student{ private int id; private String name; private Set<StudentSubject> studentSubjects; public Student() { } public Student(String name) { this.name = name; studentSubjects = new HashSet<>(); } @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true) public Set<StudentSubject> getStudentSubjects() { return studentSubjects; } public void setStudentSubjects(Set<StudentSubject> studentSubjects) { this.studentSubjects = studentSubjects; } } |
Subject entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package com.javabycode.jpa.model; import javax.persistence.*; import java.util.Set; @Entity public class Subject { private int id; private String name; private Set<StudentSubject> studentSubjects; public Subject(){ } public Subject(String name){ this.name = name; } @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @OneToMany(mappedBy = "subject") public Set<StudentSubject> getStudentSubjects() { return studentSubjects; } public void setStudentSubjects(Set<StudentSubject> studentSubjects) { this.studentSubjects = studentSubjects; } } |
StudentSubject entity(mapping to the join table with extra columns)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.javabycode.jpa.model; import javax.persistence.*; import java.io.Serializable; import java.util.Date; @Entity @Table(name = "student_subject") public class StudentSubject implements Serializable{ private Student student; private Subject subject; private Date registeredDate; @Id @ManyToOne @JoinColumn(name = "student_id") public Student getStudent() { return student; } public void setStudent(Student student) { this.student = student; } @Id @ManyToOne @JoinColumn(name = "subject_id") public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } @Column(name = "published_date") public Date getPublishedDate() { return registeredDate; } public void setPublishedDate(Date registeredDate) { this.registeredDate = registeredDate; } } |
Dig deeper:
@Table annotation maps the entity with the table. If no table annotation is present, the JPA implementation will auto assign a table for the class. It means that the class name of the entity maps with the table.
@Id annotation marks the identifier property of the entity.
@Column application maps the entity’s field with the table’s column. If no column annotation is present, the JPA implementation will auto assign a column for the class. It means that the field name of the entity maps with the table’s column.
@ManyToMany annotation define a many-to-many relationship between 2 entities. All ManyToMany relationships require a JoinTable. The JoinTable is defined using the @JoinTable annotation. The JoinTable defines a foreign key to the source object’s primary key (joinColumns), and a foreign key to the target object’s primary key (inverseJoinColumns). Normally the primary key of the JoinTable is the combination of both foreign keys.
Spring Data JPA Repository
In this example, we all need to extend the JpaRepository. This is a built-in Repository implemented some common functions to work with database: findOne, findAll, save,etc.
StudentRepository
1 2 3 4 5 6 7 |
package com.javabycode.jpa.repository; import com.javabycode.jpa.model.Student; import org.springframework.data.jpa.repository.JpaRepository; public interface StudentRepository extends JpaRepository<Student, Integer>{ } |
SubjectRepository
1 2 3 4 5 6 7 |
package com.javabycode.jpa.repository; import com.javabycode.jpa.model.Subject; import org.springframework.data.jpa.repository.JpaRepository; public interface SubjectRepository extends JpaRepository<Subject, Long>{ } |
Properties Configuration
1 2 3 4 5 6 |
spring.datasource.url=jdbc:mysql://localhost/example_manytomany spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true |
Create SpringBootApplication class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
package com.javabycode.jpa; import com.javabycode.jpa.model.Student; import com.javabycode.jpa.model.StudentSubject; import com.javabycode.jpa.model.Subject; import com.javabycode.jpa.repository.StudentRepository; import com.javabycode.jpa.repository.SubjectRepository; import com.javabycode.jpa.repository.StudentRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import javax.transaction.Transactional; import java.util.Date; @SpringBootApplication public class MyJpaApplication implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(MyJpaApplication.class); @Autowired private StudentRepository studentRepository; @Autowired private SubjectRepository subjectRepository; public static void main(String[] args) { SpringApplication.run(MyJpaApplication.class, args); } @Override @Transactional public void run(String... strings) throws Exception { Student studentD = new Student("David Pham"); Subject subjectM = new Subject("Math"); Subject subjectJ = new Subject("Java Programming"); StudentSubject studentSubjectM = new StudentSubject(); studentSubjectM.setStudent(studentD); studentSubjectM.setSubject(subjectM); studentSubjectM.setRegisteredDate(new Date()); studentD.getStudentSubjects().add(studentSubjectM); subjectRepository.save(subjectM); StudentSubject studentSubjectJ = new StudentSubject(); studentSubjectJ.setStudent(studentD); studentSubjectJ.setSubject(subjectJ); studentSubjectJ.setRegisteredDate(new Date()); studentD.getStudentSubjects().add(studentSubjectJ); subjectRepository.save(subjectJ); studentRepository.save(studentD); // dump data for (StudentSubject ss : studentD.getStudentSubjects()) { System.out.println("Student: "+ ss.getStudent().getName()); System.out.println("Subject: "+ ss.getSubject().getName()); System.out.println("Registered Date: "+ ss.getRegisteredDate()); } } } |
DEMO
You can go to the project directory on the console screen and run the command line
1 |
mvn spring-boot:run |
Or create the unit test class, for example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.javabycode.jpa; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = MyJpaApplication.class) public class JpaApplicationTests { @Test public void contextLoads() { } } |
Run the above test class and see the output like below
1 2 3 4 5 6 |
Student: David Pham Subject: Java Programming Registered Date: Sun Apr 23 17:52:38 ICT 2017 Student: David Pham Subject: Math Registered Date: Sun Apr 23 17:52:38 ICT 2017 |
That’s all on the Spring Boot JPA Many To Many Extracolumns Relationship Mapping Example.
References
Many-to-Many Relationships
Spring Boot – Database initialization
Download complete source code, click link below
springboot-jpa-manytomany-extracolumns-mysql.zip (362 downloads)
Thanks, great article.