Using Spring and Hibernate to Map out relationships

Posted on Saturday 30th October 2021


 

Intro

Spring along with Hibernate is used in countless enterprise applications. Today we'll be learning how we can implement some standard database mappings using it. This guide will use MySQL as an example DB, but it would be trivial to port to any other relational database by just changing the dependencies and configuration in the Pom file.

 

Requirements

  • Java 17
  • A running MySQL instance. If you want to follow along exactly with this guide, the command below should set you up with a database running on port 3306
  • docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:tag

 

Boilerplate

We'll need some basic scaffolding for our project. Let's start by adding the usual folder structure, POM file and check our configuration is all working as expected. A full example of this project can be found at the Github repository here

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 <?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.5.3</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.jimmyblogs</groupId> <artifactId>spring-hibernate-example</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </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> <version>8.0.27</version> </dependency> <!-- Compilation Dependencies--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>compile</scope> </dependency> </dependencies> </project>

Main.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.jimmyblogs; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } @Bean public CommandLineRunner commandLineRunner(ApplicationContext ctx) { return args -> System.out.println("The application has started successfully!"); } }

application.properties

1 2 3 4 5 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/Jimmyblogs?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password=my-secret-pw spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=com.mysql.jdbc.Driver

At this point we should be able to run the application from the Main method. If everything is working as expected, you should see the console message:
"The application has started successfully!"

 

Scenario

Let's create a scenario where we have been asked to design a Database for an office, and it's employees. Let's start with the initial Employee entity.

Employee.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.jimmyblogs.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Employee { @Id @GeneratedValue Integer id; String name; Double salary; }

Adding this class, we are creating an Employee Entity with three columns.

  • An Auto-Generated (int) field called 'id'
  • A varchar(255) field called 'name'
  • A double field called 'salary'

A picture showing the employee table with three columns

If we run the application again, we should see the table get created automatically.

 

One-to-One mapping.

So let's create our first mapping. A one-to-one mapping is where a column in one table has only one relationship with a column in another table. Taking our Employee example, let's picture that one Employee is directly associated with one Client. And a single Client is assigned exactly one Employee to look after them.

Let's implement this in Java, we're going to need a new Entity class called Client so let's create that first.

Client.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.jimmyblogs.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Client { @Id @GeneratedValue Integer id; String name; Double contractAmount; }

 

If we were to run this application again, our second table client would be created but our tables are currently unrelated.

A picture showing the employee and client tables unrelated

 

Let's add the mappings

Employee.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.jimmyblogs.entity; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Employee { @Id @GeneratedValue Integer id; String name; Double salary; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "client_id", referencedColumnName = "id") Client client; }

Client.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.jimmyblogs.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Client { @Id @GeneratedValue Integer id; String name; Double contractAmount; @OneToOne(mappedBy = "client") Employee employee; }

So let's take a look at what's changed here
On line 21 in the Employee class, we declare that we want to create a one-to-one mapping.
On line 22 we add the annotation @JoinColumn and specify which column we would like to map. In this case we are mapping on the id column and a new column in our Client table will be created with the column name client_id

If all is working properly, you should end up with something that looks like this.

A picture showing the employee and client related

 

One-to-Many mapping.

One-to-many mappings are for more common, for this example we're going to use an example of a Department. An Employee can only be a member of one Department, yet one Department can have many Employees. Therefore, this relationship is a one-to-many relationship.

Let's edit our code to reflect this

Employee.java

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 package com.jimmyblogs.entity; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Employee { @Id @GeneratedValue Integer id; String name; Double salary; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "client_id", referencedColumnName = "id") Client client; @ManyToOne @JoinColumn(name="department_id", nullable=false) Department department; }

Here we've added the @ManyToOne annotation on line 27 and told hibernate that we want to map the relationship by Department.
So in this case many employees can be related to one Department and one Department can be shared by many employees.

 

Department.java

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 package com.jimmyblogs.entity; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Department { @Id @GeneratedValue Integer id; String name; String code; Double budget; @OneToMany(mappedBy="department") private Set<Employee> employees; }

In the Department class, we've added the @OneToMany annotation on line 22. We also specify that we wish to map this relationship by Department. So in this case one Department can be related to many Employees as represented by the Set<Employee> field.

 

Our database should now be starting to look like this

A picture showing many employees related to one department

 

Many to Many mapping

The final relationship type we will focus on are many-to-many relationships. In our contrived example, we're going to add a requirement where we need to store the training courses that each Employee has been on. Each Employee can undertake many training courses and each training Course can be undertaken by many employees. Therefore, we have a many-to-many relationship between Course and Employee

Let's map that out by adapting our Employee entity as well as creating a new Course Entity

Employee.java

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 package com.jimmyblogs.entity; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Employee { @Id @GeneratedValue Integer id; String name; Double salary; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "client_id", referencedColumnName = "id") Client client; @ManyToOne @JoinColumn(name="department_id", nullable=false) Department department; @ManyToMany(cascade = { CascadeType.ALL }) @JoinTable( name = "employee_course", joinColumns = { @JoinColumn(name = "employee_id") }, inverseJoinColumns = { @JoinColumn(name = "course_id") } ) Set<Course> courses; }

Course.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.jimmyblogs.entity; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import lombok.NoArgsConstructor; @NoArgsConstructor @Entity public class Course { @Id @GeneratedValue Integer id; String name; Integer duration; @ManyToMany(mappedBy = "courses") Set<Employee> employees; }

To create the relationship type of many-to-many, we're going to generate a junction table via Hibernate which will contain a mapping of each EmployeeId and CourseId. We do this within the Employee class;

  • On line 35 we declare the junction table name of employee_course. This is the name we will give our junction table
  • On line 36 we declare one column in the junction table with a foreign key to employee_id and on line 37, another column with a foreign key to course_id.

Having a junction table like this means we can model the many-to-many relationship that exists between Employee and Course.

The Course class is simple as we have specified a junction table in the Employee class. For this we can simply tell hibernate to map our Course entities by our employees via the mappedBy property.

After running this, you should end up with something that looks the same as below

A picture showing the many to many junction table between Course and Employee

As you can see, a junction table is automatically created to model our many-to-many relationship between Employee and Course.

 

Conclusion

We have learnt various database relationship styles and how to implement them via hibernate.