In Spring Boot, a One-To-One relationship is one in which both entities
are associated with each other. In this tutorial, we will learn how to
implement a One-To-One relationship between entities in Spring
Boot using JPA and Hibernate.
1. What is a One-To-One association
Before delving into the specifics of creating relationships between entities
in Hibernate, consider how we create relationships between objects in JAVA.
An object association in JAVA is very simple: we use an attribute (variable)
of a class to do so. The definitions of two simple classes,
Factory and Product, are provided below:
// Factory class
public class Factory {
…
// Factory can make product
private Product product;
…
}
// Product class
public class Product {
…
private String productName;
…
}
We've declared a Factory POJO with a Product class attribute variable
(product). In JAVA, we can create a relationship (association)
between the Factory and Product classes using attributes.
A One-To-One relationship is when one JAVA object is linked to
another. A motorcycle, for example, has an engine, and each engine belongs
to a specific motorcycle; similarly, any location on Earth must have a
geolocation, and the geolocation refers to a specific location.
Consider for a moment how we can store the objects in a relational database.
Because traditional databases store data in two-dimensional table format
(rows and columns), relationships between tables are maintained using the
primary key and foreign key. However, objects store data in variables, and
we cannot store object associations directly in database tables using our
traditional JDBC APIs.
We can solve this problem with the help of an object-relational mapping
(ORM) tool. Hibernate is one of the most widely used ORM
tools. We can use Hibernate to automate the process of saving the
association of JAVA objects to database tables, as well as accessing
database table data and converting it back into JAVA objects. In Spring
Boot, we use some special annotations to convert our plain JAVA class into a
persistent class or entity, and then use Hibernate to access and manipulate
the data. Hibernate has implemented JPA (JAVA Persistence API) and
uses it for data manipulation to interact with relational databases. JAVA
defines JPA as an object-relational mapping specification. Hibernate
provides us with a collection of classes and methods for data persistence
with this specification.
Now it is time to make our hands dirty. Let’s dive into coding.💪
2. Spring Boot project creation
We will use
Spring Initializr
to create a new Spring Boot project, which will generate a basic structure
for our Spring Boot project. The following dependencies have been added:
- Spring Boot DevTools - necessary development tools
- Spring Web - for Spring MVC and embedded Tomcat that will run the Spring Boot application
- Spring Data JPA - Java Persistence API
- MySQL Driver - JDBC driver for MySQL (for other DB you have to choose that dependency for that DB)
Then, to download the project zip file, click GENERATE. Unzip the zip
archive. Import the project as a Maven project into Eclipse/STS.
3. Connect to the Database
We'll put the connection information in the application because we're
using MySQL as our database. Hibernate will use this information to
connect to the database as the application.properties file has a
name/value pair. The connection information is described in the
following snippet:
Here, we've set datasource.url to the URL of our JDBC connection. The
database credentials are mentioned in datasource.user and
datasource.password.
Spring Boot can gather the necessary information about the database from the
connection URL, so it is not necessary to specify
datasource.driver-class-name. However, we will be safer if we specify
the driver-class-name.
When the application is running, jpa.show-sql displays Hibernate SQL
queries in the console, jpa.hibernate.ddl-auto is set to update,
which updates the database schema every time we restart the application, and
hibernate.dialect indicates which database dialect we are using.
👉 To demonstrate, we'll make two entities: the Motorcycle and the
Engine. Then, between these two entities, establish a
One-To-One relationship.
4. Entities
First, we'll add annotations to the Engine entity to make it
persistent.
@Entity
@Table(name = "ENGINE")
public class Engine {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int Id;
@Column(name = "engine_type")
private String engineType = "";
@Column(name = "capacity")
private String capacity = "";
@Column(name = "max_torque")
private String maxTorque = "";
public Engine() {
}
public Engine(String engineType, String capacity, String maxTorque) {
this.engineType = engineType;
this.capacity = capacity;
this.maxTorque = maxTorque;
}
public int getId() { return Id; }
public void setId(int id) { Id = id; }
public String getEngineType() { return engineType; }
public void setEngineType(String engineType) { this.engineType = engineType; }
public String getCapacity() { return capacity; }
public void setCapacity(String capacity) { this.capacity = capacity; }
public String getMaxTorque() { return maxTorque; }
public void setMaxTorque(String maxTorque) { this.maxTorque = maxTorque;}
@Override
public String toString() {
return "Engine{" +
"Id=" + Id +
", engineType='" + engineType + '\'' +
", capacity='" + capacity + '\'' +
", maxTorque='" + maxTorque + '\'' +
'}';
}
}
The Engine class, as you can see, is a simple JAVA class with some
private variables, a constructor, some getter and setter methods, and an
overridden toString() method. The main thing to notice is that we used
annotations (metadata about a class) such as @Entity, @Table,
@Id, and so on.
We convert a simple JAVA class into a persistent class by using the
@Entity annotation.
@Table refers to our database table (ENGINE).
The @Id attribute is used to specify the primary key (object
identifier). The primary key is defined as an Id variable in the
Engine class.
@Column annotation is used to define column names, size, and whether or
not null is allowed for a specific column of a database table. This annotation
is required if we want the column name of the database table to differ from
the variable name (Id).
We have annotated the Id variable with @GeneratedValue. This annotation
specifies the strategy for generating the value of the primary key
(id). We applied GenerationType.IDENTITY to the primary key,
which is equivalent to MySQL's AUTO_INCREMENT.
When we run the project, Hibernate will create a table named
ENGINE with the primary key id (AUTO_INCREMENT) and other
columns with the names we specify.
Now, the Motorcycle entity with annotations:
@Entity
@Table(name = "MOTOR_CYCLE")
public class Motorcycle {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int Id;
@Column(name = "model_name")
private String modelName = "";
@Column(name = "manufacturer_name")
private String manufacturerName = "";
@Column(name = "gear_box")
private String gearBox = "";
@Column(name = "wheels")
private String wheels = "";
@Column(name = "length")
private String length = "";
@Column(name = "height")
private String height = "";
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "engine_id")
private Engine engine;
public Motorcycle() {
}
public Motorcycle(String modelName, String manufacturerName, String gearBox, String wheels,
String length, String height) {
this.modelName = modelName;
this.manufacturerName = manufacturerName;
this.gearBox = gearBox;
this.wheels = wheels;
this.length = length;
this.height = height;
}
public int getId() { return Id; }
public void setId(int id) { Id = id; }
public String getModelName() { return modelName; }
public void setModelName(String modelName) { this.modelName = modelName; }
public String getManufacturerName() { return manufacturerName; }
public void setManufacturerName(String manufacturerName) { this.manufacturerName = manufacturerName; }
public String getGearBox() { return gearBox; }
public void setGearBox(String gearBox) { this.gearBox = gearBox; }
public String getWheels() { return wheels; }
public void setWheels(String wheels) { this.wheels = wheels; }
public String getLength() { return length; }
public void setLength(String length) { this.length = length; }
public String getHeight() { return height; }
public void setHeight(String height) { this.height = height; }
public Engine getEngine() { return engine; }
public void setEngine(Engine engine) { this.engine = engine; }
@Override
public String toString() {
return "Motorcycle{" +
"Id=" + Id +
", modelName='" + modelName + '\'' +
", manufacturerName='" + manufacturerName + '\'' +
", gearBox='" + gearBox + '\'' +
", wheels='" + wheels + '\'' +
", length='" + length + '\'' +
", height='" + height + '\'' +
", engine=" + engine +
'}';
}
}
We used the annotations @OneToOne and @JoinColumn in the
persistent Motorcycle class.
The @OneToOne annotation is used to link one JAVA object to another. In
other words, if we look at it from a database standpoint, one row of one table
is exactly related to one row of another table, and vice versa.
In this example, we will establish a connection between the
Motorcycle and the Engine. In this case, one entity is dependent
on another entity in a relationship; in other words, there is no meaning of an
Engine without the Motorcycle. This is a recursive relationship. So, by
specifying CascadeType.ALL, we can simultaneously perform
INSERT, PERSIST, MERGE, DELETE, and
REFRESH operations on both related entities.
The @JoinColumn annotation is used to specify a foreign key by name
(engine_id).
As a result, Hibernate will join the MOTOR_CYCLE table and the
ENGINE table with a foreign key named engine_id as an attribute,
and the engine_id column will be created in the
MOTOR_CYCLE table to store the primary key value from the
ENGINE table.
5. Repository
The repository in Spring Boot is the data access layer that allows us to
interact with our real database for operations like insert, update, and delete
using Spring Data JPA. We have significantly reduced the number of
boilerplate codes required to perform database operations as our
MotorcycleRepository extends JpaRepository.
Here is our MotorcycleRepository:
public interface MotorcycleRepository extends JpaRepository<Motorcycle, Integer> {
}
6. Controller
A controller is a class that has one or more public methods. Controllers are
typically placed in the Controller directory. If a class is annotated with
@Controller or @RestController in Spring Boot, it will serve as
a controller, and its public methods will be exposed as HTTP endpoints if they
are annotated with @PostMapping or @GetMapping.
As a result, an HTTP GET request to
http://localhost:PORT/method-name invokes the @GetMapping method
of the ExampleController class.
The following code snippet is for MotorcycleController:
@RestController
@RequestMapping("/motorcycle")
public class MotorcycleController {
@Autowired
private MotorcycleRepository motorcycleRepository;
@PostMapping("/saveMotorcycle")
public Motorcycle saveMotorcycle(@RequestBody Motorcycle motorcycle) {
System.out.println("Motorcycle save called...");
Motorcycle outMotorcycle = motorcycleRepository.save(motorcycle);
System.out.println("Saved Motorcycle :: " + outMotorcycle);
return outMotorcycle;
}
}
The Spring Boot framework is used to create RESTful web services. When we
use a RESTful web service, the HTTP request (represented as a URL) is routed
to a specific controller. To make MorotcycleController a controller,
we annotated it with @RestController. We mapped
MotorcycleController to /motorcycle using the
@RequestMapping annotation. The controller class is made up of
several public methods that are served as HTTP requests. As a result, the
saveMotorcycle() method has a @PostMapping annotation and is
mapped to /saveMotorcycle. The annotation @PostMapping is
synonymous with HTTP POST.
An HTTP POST request to
http://localhost:PORT/motorcycle/saveMotorcycle invokes the
MotorcycleController class's @PostMapping (saveMotorcycle()) method.
We used the @Autowired annotation to inject the
MotorcycleRepository into our controller.
7. Run the Project
To run the project in Visual Studio Code, follow these steps:
- Open SpringbootonetoonemappingApplication.java.
- Click on Run to run the Java program.
To run the project in Eclipse, follow these steps:
- Right-click on SpringbootmanytomanyApplication.java.
- Then choose Run As, then click on Spring Boot App.
Take a look at the audit log in the console (Eclipse/VS Code): As you can see,
Hibernate has created two tables - ENGINE and MOTOR_CYCLE - with
specified column names and modified the MOTOR_CYCLE table to add a
foreign key that references the ENGINE table.
Because we did not specify a port in the application.properties file,
our project will run on port 8080, and the default URL to access any
REST API in this project is
http://localhost:8080/. Because our project runs on our local machine, we used localhost. If your
project is running on a remote server or in EC2, you must use the remote
server's or EC2's IP address or the elastic IP address.
8. Testing of the REST APIs
Now we'll put those REST APIs we built in the MotorcycleController to
the test.
👉 Save Motorcycle Details
In Postman, we call the following URL to save motorcycle details as well as
engine details:
http://localhost:8080/motorcycle/saveMotorcycle
Let’s see the audit logs in Console:
Hibernate saves Engine data first, followed by Motorcycle data, as shown in
the console. Hibernate obtains the value of the primary key column from the
ENGINE table and saves it in the MOTRO_CYCLE table's foreign key
column.
👉 Let's look at how we can get the Motorcycle details as well as the Engine.
To accomplish this, we must modify the MotorcycleRepository and
MotorcycleController classes.
Repository - Modification
The code of the modified MotorcycleRepository is as follows:
public interface MotorcycleRepository extends JpaRepository<Motorcycle, Integer> {
@Override
Optional<Motorcycle> findById(Long aLong);
}
Controller - Modification
The modified MotorcycleController is as follows:
@RestController
@RequestMapping("/motorcycle")
public class MotorcycleController {
@Autowired
private MotorcycleRepository motorcycleRepository;
@PostMapping("/saveMotorcycle")
public Motorcycle saveMotorcycle(@RequestBody Motorcycle motorcycle) {
System.out.println("Motorcycle save called...");
Motorcycle outMotorcycle = motorcycleRepository.save(motorcycle);
System.out.println("Saved Motorcycle :: " + outMotorcycle);
return outMotorcycle;
}
@GetMapping("/getMotorcycle/{id}")
public Optional<Motorcycle> getMotorcycle(@PathVariable String id) {
System.out.println("Motorcycle get() called...");
Optional<Motorcycle> outMotorcycle = motorcycleRepository.findById(Long.valueOf(id));
System.out.println("Motorcycle with Engine :: " + outMotorcycle);
return outMotorcycle;
}
}
In this case, we've simply added a new GetMapping to retrieve
Motorcycle details from the database table by passing the id (primary
key MOTOR_CYCLE table) value.
Testing - Again
👉 Fetch Motorcycle Details
To get the details of a specific motorbike as well as its engine, we use
Postman and the following URL with an id value of 1:
http://localhost:8080/motorcycle/getMotorcycle/1
And the audit logs in the console:
Hibernate generates a select SQL query by joining the
MOTOR_CYCLE table with the ENGINE table on the
MOTOR_CYCLE table engine_id (foreign key) column and the
ENGINE table id (primary key) column, as shown in the
console.
9. Conclusion
This is a simple example of using JPA and Hibernate to demonstrate a
unidirectional One-To-One relationship in Spring Boot. You may need to
customize and extend the implementation based on your use case.
Now what is unidirectional.
Here, we link Product to Factory by declaring a
Product attribute (variable) in Factory. As a result, when we
retrieve the Factory details, we also obtain the
Product details. However, we cannot obtain Factory information
by querying the Product. This is also true for the
motorcycle and engine examples. This is known as a
unidirectional relationship. In the following tutorial, we'll go over
bi-directional One-To-One
relationships in Spring Boot.
You can download the
source code.
Happy coding!!! 😊
Great....easy to understand : )
ReplyDelete