One To Many (Bi-directional) Relational Mapping with Spring Boot + Spring Data JPA + H2 Database

In this tutorial , we will see One To Many mapping of two entities using Spring Boot and Spring Data JPA using H2 database.

GitHub Link 

Tools:

  • Spring Boot
  • Spring Data JPA
  • H2 Database
  • IntelliJ IDEA
  • Maven

We have two entities Student and Department. Many students are part of one Department (Many To One). And vice a versa i.e. each
Department has many students in it (One To Many) .

This is called One To Many and Many To One mapping between two entities. For each of this entity, table will be created in database. So there will be two tables created in database.

create table address (id bigint generated by default as identity, name varchar(255), primary key (id));
create table student (id bigint generated by default as identity, mobile integer, name varchar(255), address_id bigint, primary key (id));
alter table student add constraint FKcaf6ht0hfw93lwc13ny0sdmvo foreign key (address_id) references address(id);

Each table has primary key as ID . Two tables has One To Many relationship between them. All student has department associated with them and each
department has many students in them.

STUDENT  table has foreign key as DEPT_ID in it. So STUDENT is a child table and DEPARTMENT table is parent table in this relationship. FOREIGN KEY constraint adds below restrictions –

  1. We can’t add records in child table if there is no entry in parent table. Ex. You can’t add department ID reference in STUDENT table if department ID is not present in DEPARTMENT table.
  2. You can’t delete the records from parent table unless corresponding references are deleted from child table . Ex. You can’t delete the entry
    DEPARTMENT table unless corresponding students from STUDENT table are deleted . You can apply ON_DELETE_CASCADE with foreign key constraint if you want to delete students from STUDENT table automatically when department is deleted.
  3. If you want to insert a student into STUDENT table then corresponding department should already be present in DEPARTMENT table.
  4. When any department is updated in DEPARTMENT table then child table will also see the updated reference from parent table

Now we will see how can we form this relationship using Spring data JPA entities.

Step 1: Define all the dependencies required for this project

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.myjavablog</groupId>
<artifactId>springboot-jpa-one-to-many-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-jpa-one-to-many-demo</name>
<description>Demo project for Spring Boot</description>
<pre><code><properties>
    <java.version>1.8</java.version>
</properties>

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

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.apache.tomcat</groupId>
                <artifactId>tomcat-jdbc</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <!--<scope>runtime</scope>-->
    </dependency>

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

    <!-- https://mvnrepository.com/artifact/colt/colt -->
    <dependency>
        <groupId>colt</groupId>
        <artifactId>colt</artifactId>
        <version>1.2.0</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build></code></pre>
</project>

spring-boot-starter-data-jpa – This jar is used to connect to database .It also has a support to provide different JPA implementations to interact with the database like Hibernate, JPA Repository, CRUD Repository etc.

h2 – Its used to create H2 database when the spring boot application boots up. Spring boot will read the database configuration from application.properties file and creates a DataSource out of it.

Step 2: Define the Model/Entity classes

Student.java

package com.myjavablog.model;

import org.springframework.stereotype.Repository;

import javax.annotation.Generated;
import javax.persistence.*;
import java.util.Optional;

@Entity
@Table(name = "STUDENT")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;

    @Column
    private int mobile;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "DEPT_ID")
    private Department department;

    public Long getId() {

        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) {

        this.name = name;
    }

    public int getMobile() {

        return mobile;
    }

    public void setMobile(int mobile) {

        this.mobile = mobile;
    }

    public Department getDepartment() {

        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}

Department.java

package com.myjavablog.model;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "DEPARTMENT")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column
private String name;

@OneToMany(mappedBy = "department")
private List<Student> studentList;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public List<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
}

Below are the Annotations used while creating the entity classes –

Spring Data JPA Annotations –

  • @Entity – This annotation marks the class annotations.
  • @Table – This annotation creates table in database.
  • @Id – It creates primary key in table.
  • @GeneratedValue – It defines primary key generation strategy like AUTO,IDENTITY,SEQUENCE etc.
  • @Column It defines column property for table.
  • @OneToMany – This annotation is used in the owning entity/Parent entity. This annotation is required in only if you want Bi-directional relational mapping.
  • @ManyToOne – This annotation creates Many to one relationship between Student and Department entities. The cascade property (CascadeType.ALL) defines what should happen with child table records when something happens with parent table records. On DELETE, UPDATE , INSERT operations in parent table child table should also be affected. Both @OneToMany and @ManyToOne annotations are required to form Bi-directional relational mapping.
  • @JoinColumn – You can define column which creates foreign key in a table. In our example, DEPT_ID is a foreign key in STUDENT table which references to ID in DEPARTMENT table.

Step 3: Create the JPA repositories to query STUDENT and DEPARTMENT tables

StudentRepository and AddressRepository implemets JpaRepository which has methods to perform all CRUD operations. It has methods like save(), find(), delete(),exists(),count() to perform database operations.

package com.myjavablog.dao;

import com.myjavablog.model.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
public Student findByName(String name);
}

package com.myjavablog.dao;

import com.myjavablog.model.Address;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AddressRepository extends JpaRepository<Address, Long>{
}

Step 4: Create the class BeanConfig to configure the bean for H2 database

This class creates a bean ServletRegistrationBean and autowires it to spring container. This bean actually required to access H2 database from console through browser.

package com.myjavablog;

import org.h2.server.web.WebServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
@Bean
ServletRegistrationBean h2servletRegistration() {
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(new WebServlet());
    registrationBean.addUrlMappings("/console/*");
    return registrationBean;
}
}

Step 5: Last but not the least, You need to define application properties.

#DATASOURCE (DataSourceAutoConfiguration & #DataSourceProperties)
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
#Hibernate
#The SQL dialect makes Hibernate generate better SQL for the chosen #database
spring.jpa.properties.hibernate.dialect =org.hibernate.dialect.H2Dialect
spring.datasource.driverClassName=org.h2.Driver
#Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE
server.port=8082
server.error.whitelabel.enabled=false

Step 6: Create the spring boot main file

package com.myjavablog;

import com.myjavablog.dao.DepartmentRepository;
import com.myjavablog.dao.StudentRepository;
import com.myjavablog.model.Department;
import com.myjavablog.model.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import java.util.Arrays;

@SpringBootApplication
public class SpringbootJpaOneToManyDemoApplication implements CommandLineRunner {
@Autowired
private DepartmentRepository departmentRepository;

@Autowired
private StudentRepository studentRepository;

public static void main(String[] args) {

    SpringApplication.run(SpringbootJpaOneToManyDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
    //Bi-directional mapping
		Department department1 = new Department();
		department1.setName("IT");

		//Students list
		Student student = new Student();
		student.setName("Danny");
		student.setMobile(33333);
		student.setDepartment(department1);
		Student student1 = new Student();
		student1.setName("Mark");
		student1.setMobile(11111);
		student1.setDepartment(department1);

		//department1.setStudentList(Arrays.asList(student,student1));
		department1.getStudentList().add(student);
		department1.getStudentList().add(student1);

		departmentRepository.save(department1);

		//Get the list of students from department
		Department department = departmentRepository.findDepartmentById(1l);

		for(Student s : department.getStudentList())
			System.out.println(s);
}
}

Step 7: Now run the application

Run the application as a java application or as spring boot application spring-boot:run

Once you run the application records will be inserted to tables as below –

Step 8: This application also has REST endpoints to expose the services as below –

Save department –

Add students under created department –

We can check database entries to cross verify –

Bi-directional Relational Mapping –

To create the bi-directional mapping between entities, we need to below changes in entities –

The idea with bidirectional one-to-many association is to allow you to keep a collection of child entities in the parent and enable you to persist and retrieve the child entities via the parent entity.

In our example, Department entity has list of students in it. So we can access all students from particular department.

//Bi-directional mapping
		Department department = new Department();
		department.setName("IT");

		//Students list
		Student student = new Student();
		student.setName("Danny");
		student.setMobile(33333);
		Student student1 = new Student();
		student1.setName("Mark");
		student1.setMobile(11111);

		department.setStudentList(Arrays.asList(student,student1));

		departmentRepository.save(department);

Hibernate automatically issues insert statements and saves the Students added to the Department.

Similarly, you could fetch Students via the Department entity like so –

    //Retrieve department
    Department department1 = departmentRepository.findDepartmentById(1l);

   // Retrieve students from Department
    System.out.println(department1.getStudentList());

When you write department1.getStudentList(), hibernate loads all the comments from the database if they are not already loaded.

Problems with bidirectional one-to-many mapping

  • A bidirectional mapping tightly couples the many-side of the relationship to the one-side.
  • In our example, If you load students via the Department entity, you won’t be able to limit the number of students loaded. That essentially means that you won’t be able to paginate.
  • If you load students via the Department  entity, you won’t be able to sort them based on different properties. You can define a default sorting order using @OrderColumn annotation but that will have performance implications.
  • You’ll have to face multiple times LazyInitializationException.

When can you use a bidirectional one-to-many mapping

A bidirectional one-to-many mapping might be a good idea if the number of child entities is limited.

Moreover, A bidirectional mapping tightly couples the many-side of the relationship to the one-side. Many times, this tight coupling is desired.

Example:

When you have to design an application for multiple choices questions then you should have two entities like Question and Choice. When you fetch the Question then Choices will also be fetched with that question. So Question and Choice has tight coupling between them.

So to decide between bidirectional and unidirectional mappings, you should think whether the entities have a tight coupling or not

Leave a Comment

Bitnami