Before delving into the creation of robust APIs, Let’s take a quick diversion to learn about how we can access a database in Spring and save and retrieve our data efficiently.
Part 2 — Link
Part 4 is now available: Here
Pre-Requisites:
- Knowledge of what is a Database and DBMS.
- A little bit of SQL basics.
Table Of Contents:
- Using a database in spring
- The easier way to use databases
- What is hibernate?
- Setting up a free database
- Setting up the Spring Boot project
- Connecting to a database
- Repositories, Entity Managers and Contexts
- CRUD operations using hibernate
Using A Database In Spring:
Spring provides a JDBC dependency for database access. Spring JDBC simplifies database interaction in Java applications by handling many of the tedious and error-prone tasks associated with database access.
JDBC stands for Java Database Connectivity. It is a Java-based API (Application Programming Interface) that allows Java applications to interact with relational databases. JDBC provides a standard interface for connecting to and interacting with databases, regardless of the specific database management system being used (e.g., MySQL, Oracle, PostgreSQL, SQL Server).

Spring Boot simplifies JDBC development even further by providing automatic configuration and initialization, making it easier to work with databases in Spring-based applications.
But there is one big problem. JDBC does involve a significant amount of boilerplate code and multiple steps to interact with a database.
When using JDBC, developers have to write a lot of boilerplate code to manage database connections, handle exceptions, and process results. Developers need to create SQL statements, which can be prone to SQL injection if not handled carefully.
This extensive boilerplate code in JDBC can make simple database operations seem overly complex and error-prone.
JDBC gives you fine-grained control over database operations, which can be advantageous in certain scenarios like when you need optimized performance.
The Easier Way:
JPA, which stands for Java Persistence API, is a Java-based specification and framework for managing relational database access in Java applications.
JPA addresses those above challenges by providing a higher-level, object-oriented approach to database interactions. It allows developers to work with Java objects that map directly to database tables, reducing the need for manual SQL query writing and database-specific knowledge.

While using JPA, you work with Java objects instead of SQL queries. JPA takes care of the process of converting those objects into table columns and methods to corresponding SQL queries. JPA can generate SQL queries for common CRUD (Create, Read, Update, Delete) operations automatically.
JPA uses JDBC internally to execute the SQL queries. However, the developer does not need to have any knowledge of it. As you can imagine, this will be slower than using plain old JDBC. But it will save a lot of sanity and speed up the development process.

You can think of JPA as an ORM(Object Relational Mapper) similar to go-orm in Golang or Mongoose in Node.js or Sequelize in Python if you are coming from one of those languages.
What’s hibernate then?
Well, Hibernate is the name of an ORM framework that became so popular that it inspired the creation of a standard.
JPA is that standard. It defines a set of interfaces, classes, and annotations that provide a common way to work with databases in Java applications. This standardization ensures consistency and portability across different ORM implementations.
There are many other ORMs based on JPA like EclipseLink, Apache OpenJPA, etc. but Hibernate is the most popular and widely used. In this tutorial, we will use Hibernate as well.
Setting Up A Database:
Before you start with JPA and Hibernate, you need to set up your development environment. Most importantly, a database.
You can use MySQL, PostgreSQL, SQLite, MongoDB it doesn’t matter. I will be using PostgreSQL. You can install it along with an admin dashboard on your local machine or use a free online managed database as I will do below.
Wait What? A Free PostgresSQL Database?
Yes, neon.tech provides a free managed PostgreSQL instance with a generous free tier. This will allow us to get started with minimal setup and installation.
- Go to https://neon.tech/
- Click on ‘Sign Up’ and Sign up using your GitHub account or Google account.
- Choose Postgres version 15. After that, Give any name for your project and your database. Select a region closest to you. I’ll choose Singapore.

Then click ‘Create Project’.
That’s it. Now we have a database ready. But we do not have any tables in it yet.
Setting Up The Spring Boot Project
Go to the Spring Initializer website and create a new project like we have been doing before.
But this time add PostgreSQL Driver and Spring Data JPA dependencies as well along with Spring Web.

Then click ‘Generate’. Unzip the downloaded project and open it in your IDE.
Connecting To The Database:
Open the src/main/resources/application.properties file and configure your PostgreSQL database connection:
spring.datasource.url=jdbc:postgresql://ep-calm-bonus-17534423.ap-southeast-1.aws.neon.tech/pokemondb
spring.datasource.username=aibenmeehan
spring.datasource.password=BNgfhwGrKS24
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=updateReplace the URL and Username and Password with yours.

- spring.datasource.url: This property defines the URL to connect to the PostgreSQL database.
jdbcindicates that you are using a JDBC (Java Database Connectivity) driver to connect to the database. - spring.datasource.username: This property specifies the username to be used when connecting to the database.
- spring.datasource.password: This property specifies the password associated with the username for the database.
- spring.datasource.driver-class-name: This property sets the JDBC driver class that Spring Boot will use to connect to the database. In this case, it’s configured to use the PostgreSQL JDBC driver, which is specified as
org.postgresql.Driver. - spring.jpa.properties.hibernate.dialect: Hibernate requires knowledge of the SQL dialect of the database it’s working with. This property sets the Hibernate dialect for PostgreSQL to ensure that Hibernate generates appropriate SQL statements for PostgreSQL.
org.hibernate.dialect.PostgreSQLDialectis the dialect class for PostgreSQL. - spring.jpa.hibernate.ddl-auto: Set to "update" to auto create the table if it doesn't exist or update it if changes are detected.
Here is how my application.properties looks after this
pokemondb is the name of my databaseNow if you run
./mvnw spring-boot:runTomcat should start without any errors if you have set all the details right.
Entities:
An “entity” refers to a Java class that is mapped to a database table.
To create a Hibernate entity, you define a Java class with the following characteristics:
- The class is annotated with
@Entity, which indicates that it is a persistent entity. - The class has a no-argument constructor.
- The class defines properties or fields that correspond to columns in a database table.
- You can use annotations like
@Id,@GeneratedValue, and others to specify the primary key and other attributes for the entity.
For example, I want to create a table of Pokemon with their name, type and level.
So I will create a file called PokemonModel.java at src\main\java\com with this code
package com.benmeehan111.hibernatedemo;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class PokemonModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String type;
private int level;
PokemonModel() {
// Default constructor
}
PokemonModel(String name, String type, int level) {
this.name = name;
this.type = type;
this.level = level;
}
// Getters and setters for id, name, type, level
}- The
Pokemonclass is annotated with@Entityto indicate that it's an entity. - The
idfield is marked with@Idto specify it as the primary key. Hibernate will then take care of making sure this value is unique when a new entry is added to the database. - The class has other fields to represent properties of the Pokémon, such as
name,type, andlevel.
You would typically configure Hibernate to map this entity class to a corresponding database table, where each row in the table represents a Pokémon with attributes like name, type, and level. Hibernate would then handle the underlying database operations, allowing you to work with Pokémon objects in a more object-oriented manner while automatically synchronizing the changes with the database.

Column Names:
You might wonder, if each field is mapped to a table column, what will be the column names for these fields?
By default, Hibernate uses the name of the attribute as the column name. For example, if you have an attribute named name, Hibernate will map it to a column named name in the database.
But if you want to change this behavior, you can use the @Column annotation. For example, if I want the column name to be different from the name field, I can do
@Column(name = "pokemon_name")
private String name;Auto-Generating Primary Key Values:
@GeneratedValue annotation is used to specify how the primary key value of an entity should be generated when a new record is inserted into the database. It is commonly used with primary key fields marked as @Id.
There are several strategies available for generating primary key values, and you can choose the one that best suits your needs. The most commonly used strategies are, GenerationType.IDENTITY (Auto-increment), GenerationType.SEQUENCE (Database Sequences), GenerationType.TABLE (Table-based) and Custom Generated Value.
With custom-generated value, you create your logic for generating primary key values by implementing the IdentifierGenerator interface.
Try to use a UUID instead of using GenerationType.IDENTITY whenever possible.
Repositories, Managers and Contexts:
Repository:
A repository is like a data storage and retrieval service in your Spring Boot application. It provides a convenient way to interact with your database. You can think of it as a bridge between your Java code and the database tables.
Repositories allow you to perform common database operations like inserting, updating, deleting, and querying data without writing complex SQL queries.
Entity Manager:
The entity manager is like a gatekeeper that handles interactions between your application and the database. It keeps track of your Java objects (entities) and their corresponding records in the database.
It’s responsible for managing the lifecycle of entities, which includes creating, updating, and deleting records in the database based on changes in your Java objects.
Persistence Context:
The persistence context is a temporary workspace where changes to entities are tracked and managed before being synchronized with the database. Think of it as a “sandbox” for your data.
These changes are only written to the database when you explicitly request it (e.g., by calling a repository’s save method). This mechanism helps to batch and optimize database updates.
How Do They All Relate?

Repositories simplify database operations, the entity manager acts as an intermediary for database interactions, and the persistence context helps manage changes to your data before committing them to the database.
Together, these components make it easier to work with databases in Spring Boot applications while abstracting many of the underlying complexities of data management.
Coding It Up:
Go ahead and create a new file PokemonRepository.java with the following
import org.springframework.stereotype.Repository;
@Repository
public interface CustomPokemonRepository {
void customInsertPokemon(String name, String type, int level);
}Now, we’ll implement this interface in a file called PokemonRepositoryImpl.java
package com.benmeehan111.hibernatedemo;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Repository
@Component
public class PokemonRepositoryImpl implements PokemonRepository {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void customInsertPokemon(String name, String type, int level) {
PokemonModel newPokemon = new PokemonModel(name, type, level);
this.entityManager.persist(newPokemon);
}
}- The
PokemonRepositoryImplclass is annotated with@Repository. This annotation tells Spring that this class is a Spring-managed component and should be automatically scanned and registered as a bean in the Spring application context. It is used to create a repository component for managing database operations related to thePokemonentity. - The
@PersistenceContextannotation is used to inject anEntityManagerinstance provided by the JPA provider (e.g., Hibernate). - Inside the
customInsertPokemonmethod, a newPokemonModelobject is created with the providedname,type, andlevel. Thepersistmethod is then called on theentityManagerto insert the newPokemonModelentity into the database.
Transactions:
The @Transactional annotation is an important part of the Spring Framework and is commonly used in the context of database transactions.
Imagine you’re dealing with a database, and you want to perform multiple actions (like saving data) as a single unit of work. You want either all of these actions to succeed or none of them to happen. That’s where transactions come in.
- A transaction is like a mini-mission with multiple steps. It’s like making a sandwich. You need to get the bread, add ingredients, and put them together. If any step fails, you want to undo everything, so you don’t end up with a half-made sandwich.
- The
@Transactionalannotation is like telling the system, "Hey, everything inside this method should happen as a single mission. If any part of the mission fails, we need to go back to the way things were before we started."

Now we can use this repository to perform CRUD operations in the database easily.
Always use @Transactional annotation when doing an insert, update or delete.
CRUD Operations:
CRUD stands for Create, Read, Update and Delete operations in the database. They are the basic 4 fundamental operations we commonly perform on a database.
Let’s see how to do these 4 fundamental operations using Hibernate.
- Creating a new Pokemon
- Getting a Pokemon from the database
- Updating a Pokemon’s name
- Deleting a Pokemon from the database
Inserting New Data:
We’ve already created the code for creating a new Pokemon in the Repository class. Now, let’s use it.
Let’s create a REST controller class using the @GetMapping annotation we know.
package com.benmeehan111.hibernatedemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PokemonController {
private final PokemonRepository pokemonRepository;
@Autowired
public PokemonController(PokemonRepository pokemonRepository) {
this.pokemonRepository = pokemonRepository;
}
@GetMapping("/insertPokemon")
public String insertPokemon(
@RequestParam String name,
@RequestParam String type,
@RequestParam int level) {
pokemonRepository.customInsertPokemon(name, type, level);
return "New Pokemon added";
}
}
This method is annotated with @GetMapping and maps to the URL path "/insertPokemon." It specifies that this method should handle GET requests to this URL. The method accepts three request parameters: "name," "type," and "level." These parameters are extracted from the URL's query string.
Inside the method, it calls a method named customInsertPokemon on the pokemonRepository object, passing the values of the request parameters as arguments.
Now in your browser, visit http://localhost:8080/insertPokemon?name=Pikachu&type=Electric&level=25
Go ahead and check your database. You should see a new table automatically created and a new row added to it like this

We’ll look at the REST controller and Mappings in-depth in the next part.
Getting Data From Database:
To get a Pokemon from the database using its primary key, it’s pretty simple. We can just use the find method of the Entity Manager.
In the PokemonRepository interface, add a new method called findPokemonById
package com.benmeehan111.hibernatedemo;
public interface PokemonRepository {
void customInsertPokemon(String name, String type, int level);
void findPokemonByID(int id);
}Now, let’s implement this in the PokemonRepositoryImpl class
public PokemonModel findPokemonByID(int id) {
return entityManager.find(PokemonModel.class, id);
}Let’s also create a new route to use this in PokemonController class
@GetMapping("/getPokemon")
public String insertPokemon(@RequestParam int id) {
return pokemonRepository.findPokemonByID(id).toString();;
}Now in your browser, visit http://localhost:8080/getPokemon?id=1, you should see the object id of the retrieved object. You can implement getters for PokemonModelclass to get specific fields if you need them.
com.benmeehan111.hibernatedemo.PokemonModel@4408ea20
More Complex Queries:
What if we want to write more complex queries? like getting all the Pokemon? or getting all Pokemon sorted by their levels?
In this case, we will use the createQuery function provided by the Entity Manager. Using this we can write our queries by using the model class name and field names.
public List<PokemonModel> findAllPokemonSorted() {
TypedQuery<PokemonModel> query = entityManager.createQuery("From PokemonModel order by level",
PokemonModel.class);
return query.getResultList();
}This will find all the Pokemon sorted by the level.
You can also use a custom parameter like user input inside the query.
public PokemonModel findPokemonByName(String name) {
TypedQuery<PokemonModel> query = entityManager.createQuery("From PokemonModel where name=:n",
PokemonModel.class);
query.setParameter("n", name);
return query.getSingleResult();
}Here we pass in the name as a parameter using : notation. The advantage of this is that, Hibernate will automatically check for any SQL Injections before replacing the parameter.
Updating Existing Data:
To update existing data in the database, we need to retrieve the entity, make changes to its properties, and then persist those changes back to the database. Let’s create a method for updating a Pokemon’s name in the PokemonRepository interface:
public interface PokemonRepository {
// other methods...
void updatePokemonName(int id, String newName);
}Now, let’s implement this method in the PokemonRepositoryImpl class:
public void updatePokemonName(int id, String newName) {
PokemonModel pokemon = entityManager.find(PokemonModel.class, id);
if (pokemon != null) {
pokemon.setName(newName); //implement this setter in the model
entityManager.merge(pokemon);
}
}In this method, we use the find method of the EntityManager to retrieve the Pokemon entity by its ID. If the Pokemon exists, we update its name and then use the merge method to persist the changes back to the database.
Now, let’s add a new mapping in the PokemonController class to update a Pokemon’s name:
@RestController
public class PokemonController {
// other fields and constructor...
@GetMapping("/updatePokemonName")
public String updatePokemonName(
@RequestParam int id,
@RequestParam String newName) {
pokemonRepository.updatePokemonName(id, newName);
return "Pokemon name updated";
}
}Now, in your browser, visit http://localhost:8080/updatePokemonName?id=1&newName=PikachuUpdated. This will update the name of the Pokemon with ID 1 to “PikachuUpdated.”
Understanding merge and update in Hibernate
Hibernate provides two distinct methods, merge and update, to manage the persistence of objects in a session. Both methods are used for updating the state of entities, but they operate in slightly different ways.
1. update Method:
The update method in Hibernate is designed to reattach a detached object to the current Hibernate session and synchronize its state with the database. Throws an exception if another persistent instance with the same identifier is already associated with the session. This is because Hibernate enforces a single representation of an entity with a given identifier within a session.
2. merge Method:
The merge method is also used to make a detached object persistent, but it operates with more flexibility regarding the state of the current session. Does not throw an exception if another persistent instance with the same identifier is already associated with the session. Instead, it returns a new managed instance.
merge is what you should be using in most scenarios.
- Persistent Entity (Database Row):
Represents an entity that is currently associated with the Hibernate session and has a representation in the database.
- Detached Object:
Represents an object that was previously associated with a Hibernate session but is now not actively managed by any session.
update:
Conceptually, the update operation reattaches a detached object to the current Hibernate session, making it persistent and synchronizing its state with the database.
merge:
Conceptually, the merge operation reattaches a detached object to the current Hibernate session, making it persistent. It handles scenarios where there might be a conflict with an already persistent instance in the session.
Deleting Data:
To delete data from the database, we need to find the entity by its ID and then remove it. Let’s add a method for deleting a Pokemon by ID in the PokemonRepository interface:
public interface PokemonRepository {
// other methods...
void deletePokemonById(int id);
}Now, implement this method in the PokemonRepositoryImpl class:
public void deletePokemonById(int id) {
PokemonModel pokemon = entityManager.find(PokemonModel.class, id);
if (pokemon != null) {
entityManager.remove(pokemon);
}
}In this method, we use the find method to retrieve the Pokemon by its ID and then use the remove method to delete it from the database.
Next, add a new mapping in the PokemonController class to delete a Pokemon by ID:
@RestController
public class PokemonController {
// other fields and constructor...
@GetMapping("/deletePokemon")
public String deletePokemon(
@RequestParam int id) {
pokemonRepository.deletePokemonById(id);
return "Pokemon deleted";
}
}Now, in your browser, visit http://localhost:8080/deletePokemon?id=1. This will delete the Pokemon with ID 1 from the database.
You can also leverage the executeQuery method for performing UPDATE and DELETE operations using native SQL statements just like we did for SELECT.
Hibernate Wrap-up:
With these CRUD operations, you now have a basic understanding of how to use Hibernate and Spring Data JPA to interact with a database in a Spring Boot application.
In the next part, we will explore the REST mappings in-depth and build a full-fledged real-world project.
“The future has several names. For the weak, it is impossible; for the fainthearted, it is unknown; but for the valiant, it is ideal.”
― Victor Hugo, Les Misérables




