Introduction
As the use of software becomes more common and more and more systems are built to handle various tasks, data plays a more important role in the current and future technology scene. Information is increasingly becoming more valuable as technology advances and opens up more opportunities for its use.
It is for this reason, and many more, that safe storage and manipulation of data have become an important aspect of any system or application that is built.
What is Object-Relational Mapping?
In many systems, real-life objects are modeled as objects in systems to ease the representation and manipulation of their attributes. For instance, a phone can be modeled as an object with attributes such as its name, operating system, manufacturer, and much more as its attributes and this can be easily manipulated and stored in a database.
Object-Relational Mapping (ORM) is a technique of mapping such objects and their attributes in the database through Object-Relational Mappers. This technique also helps us convert data between incompatible systems using object-oriented programming applications.
An ORM is a library that helps us interact with multiple databases or systems easily using our language of choice. Our code is now mapped to the databases' specific query language.
Normally, we need to use a database-specific language to interact with a database. For example, to interact with a MySQL database, we need to use the Structured Query Language (SQL), but these languages may differ from platform to platform.
For example, while they are still similar, the syntax on a PostgreSQL database is different from the query language used on a Microsoft SQL database. An ORM helps bridge that difference and plug in our software into different database systems with ease.
Other benefits of using an ORM includes speeding up of the development process since the developers do not have to write the database access code and repeat it every time they want to access a database. Once a model is designed and manipulation code is written, it does not need to be done again, hence making the code easy to update, maintain, and reuse.
However, there are some drawbacks associated with ORMs and they include:
- ORMs have a tendency to be slow in some situations performance-wise
- For complex queries like joins, ORMs sometimes cannot substitute raw SQL queries
- Due to the abstractions introduced by an ORM, the developer may lose understanding of SQL and how database management is achieved behind the scenes
Hibernate
Hibernate is a framework that enables developers to easily persist application data in relational databases using JDBC. It is an implementation of the Java Persistence API (JPA), which means that it can be utilized in any system that supports JPA, such as the standard edition (Java SE) and the enterprise edition (Java EE).
Hibernate is a lightweight and open-source tool that simplifies the creation, manipulation, and access of data from a database in Java-based applications. It works by mapping an object created from a Java class and its attributes, to data stored in the database.
Some advantages of using Hibernate include:
- It is open-source and lightweight, which means that it's free to use and has a community of contributors constantly improving it
- Hibernate utilizes a cache internally that enhances its performance
- It is database-independent, which means it can be used to access and manipulate data in various different databases
- It provides the functionality to simplify joins when fetching data from multiple tables
- By creating tables automatically, the developer can focus on doing other logic
- It's a stable framework that has been around for 18 years
Alternatives
Hibernate is not the only ORM framework that we can use in our Java applications, others include:
- JOOQ (Java Object Oriented Querying) is a light database-mapping software library
- JDBI provides access to relational data in Java in a convenient way
- MyBatis is an SQL mapper framework to integrate relational databases
- Ebean which can be used for both Java and Kotlin-based applications
- ORMLite which is a lightweight framework to persist Java objects to SQL databases
These are but a few of the alternatives for Hibernate, there are definitely even more libraries and frameworks out there that suit many different scenarios and databases.
Implementing Hibernate with Spring Boot
Project Setup
For this demo project, we are going to use a PostgreSQL database and installation instructions can be found here for Mac OS, Linux, and Windows platforms.
Once set up, we can create our demo database, phonesdemo
. PgAdmin provides a user interface to interact with a PostgreSQL database, but a terminal can also be used.
Let us now see Hibernate in action by using it in a sample Spring Boot API, which we will bootstrap using the Spring Initializr tool:
We will be using Java 8 and Maven for our dependency management with a few dependencies:
Spring Web Starter
to help us build a web-based applicationSpring Data JPA
to provide the data access API that Hibernate will utilizeH2 Database
to bring in Hibernate functionality into our projectPostgreSQL
to enable us to connect to a PostgreSQL database
Once we click on "Generate", we will receive a zip file containing the project and we can start implementing the web application that requires a database. Once we unpack this zip file into our working folder, we can test that it is ready to be worked on by running the command:
$ mvn spring-boot:run
Implementation
Let us modify our application.properties
to include our database details:
# Database Properties
spring.datasource.url=jdbc:postgresql://localhost:5432/phonesdemo
spring.datasource.username=postgres
spring.datasource.password=
# Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL92Dialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=update
Hibernate provides an H2 Console that we can use to check the status of the database and even perform data entry via a user interface. We enable it by adding the following line to our application.properties
:
spring.h2.console.enabled=true
Then we fire up our application and navigate to http://localhost:8080/h2-console
to test if everything is working. We get a page where we can test if the connection to our database works:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
For the saved settings dropdown, we are going to choose Generic PostgreSQL and we are also going to update the JDBC URL to match the name of our test database. Then we fill in our database username and password and click on "Test Connection" just to make sure that our Spring Application can connect to our database. If everything is well set up, we get a success message.
If we click on "Connect", we get this page:
Here we can navigate our database and even perform SQL queries.
The API we will be building will be used to store and manipulate phones and their attributes such as name and operating system. With our database in place and connected, let us create an entity class (Phone.java
) that will map our object's attributes to the database and enable us to perform CRUD operations in the database:
// Imports truncated for brevity, refer to GitHub link below for the full code
@Entity
@Table(name = "phones")
@EntityListeners(AuditingEntityListener.class)
public class Phone {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id; // Each phone will be given an auto-generated unique identifier when stored
@Column(name = "phone_name", nullable = false)
private String phoneName; // Save the name of the phone
@Column(name = "os", nullable = false)
private String os; // Save the operating system running in the phone
// Standard getters and setters
}
The @Entity
annotation tells Hibernate that this class represents an entity that should be persisted.
The @Table
annotation is used for naming the table. If this annotation is omitted, the table will simply use the name of the class/entity.
Similarly the @Column
annotations can also be omitted, but the database columns will use the field names as they are, and sometimes this isn't the preferred behavior since your column names might be snake case and field names are camel case, for example.
When we save this file and restart our server and check our database, there will be a new table called phones
and the columns of id
, phone_name
, and os
will be present.
There will be no data but this is Hibernate at work, if the table specified in the entity class does not exist, Hibernate will create it for us.
Let us now implement the controller to help us perform operations on our database through an API:
@RestController
@RequestMapping("/api/v1")
public class PhoneController {
@Autowired
private PhoneRepository phoneRepository;
// GET method to fetch all phones
@GetMapping("/phones")
public List<Phone> getAllPhones() {
return phoneRepository.findAll();
}
// GET method to fetch phone by Id
@GetMapping("/phones/{id}")
public ResponseEntity<Phone> getPhoneById(@PathVariable(value = "id") Long phoneId)
throws Exception {
Phone phone = phoneRepository.findById(phoneId)
.orElseThrow(() -> new Exception("Phone " + phoneId + " not found"));
return ResponseEntity.ok().body(phone);
}
// POST method to create a phone
@PostMapping("/phones")
public Phone createPhone(@Valid @RequestBody Phone phone) {
return phoneRepository.save(phone);
}
// PUT method to update a phone's details
@PutMapping("/phones/{id}")
public ResponseEntity<Phone> updatePhone(
@PathVariable(value="id") Long phoneId, @Valid @RequestBody Phone phoneDetails
) throws Exception {
Phone phone = phoneRepository.findById(phoneId)
.orElseThrow(() -> new Exception("Phone " + phoneId + " not found"));
phone.setPhoneName(phoneDetails.getPhoneName());
phone.setOs(phoneDetails.getOs());
final Phone updatedPhone = phoneRepository.save(phone);
return ResponseEntity.ok(updatedPhone);
}
// DELETE method to delete a phone
@DeleteMapping("/phone/{id}")
public Map<String, Boolean> deletePhone(@PathVariable(value="id") Long phoneId) throws Exception {
Phone phone = phoneRepository.findById(phoneId)
.orElseThrow(() -> new Exception("Phone " + phoneId + " not found"));
phoneRepository.delete(phone);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}
In our controller class, we annotate our class by @RestController
to indicate that this is the request handler class that will be handling the REST functionality for our API. We then define methods to handle each of the four RESTful operations: GET
, POST
, PUT
, and DELETE
. These methods will provide an interface for us to interact with our API and manage data.
Our API, in turn, will utilize Hibernate to reflect our operations on the said data to our database.
Let us start by creating a single phone through our API:
We get the response from our API, but let us check the database using the H2 Console to confirm:
As you can see, from the screen-shot above, to fetch the data in our database, we use the SQL command SELECT * FROM phones
. In order to achieve the same in our code through the ORM, it is as simple as using the line:
phoneRepository.findAll();
This is more friendly and familiar to us since it is achieved in the same language that we are using while implementing the rest of our project.
Conclusion
We have successfully created a phone object and saved its attributes to our PostgreSQL database by using Hibernate in our Spring Boot API. We can add more phones, delete phones, and update phone data by interacting with the API and Hibernate will reflect the changes in our database.
Hibernate has made it easier for us to interact with a database from our Spring Boot application and manage our data. The development time has also been significantly reduced since we do not have to write the SQL commands to manage the data ourselves.
The source code for this project is available here on GitHub.