UNDERSTANDING THE REAL
DIFFERENCE BETWEEN
JPA AND HIBERNATE
When I first learned Spring Boot, I used Spring Data JPA everywhere. For a long time, I couldn’t see any difference between them. Honestly, I thought people were just overcomplicating it.
This article is what I wish someone had explained to me earlier.
Hibernate: The Engine That Actually Does the Work
Hibernate is a real ORM framework
It’s the thing that:
- check_circle Converts Java objects into database rows
- check_circle Tracks entity state (new, managed, detached)
- check_circle Decides when to run SQL
- check_circle Handles lazy loading, caching, dirty checking
If you use Hibernate directly, your code looks like this:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = session.get(User.class, 1L);
user.setName("Joekawai");
tx.commit();
session.close();You are very close to the database:
- check_circle You manage sessions
- check_circle You manage transactions
- check_circle You control queries
Hibernate gives you power, but power comes with responsibility.
I’ll show it step by step so you see what Hibernate is really doing.
1. User Entity
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// getters & setters
}Nothing special here — standard JPA annotations (Hibernate understands them).
2. Fetching List of Users with Hibernate (HQL)
This is the classic and most common way.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
List<User> users = session
.createQuery("FROM User", User.class)
.getResultList();
tx.commit();
session.close();What’s happening behind the scenes:
- check_circle Hibernate opens a Session
- check_circle Converts FROM User → SQL (SELECT * FROM users)
- check_circle Maps rows → User objects
- check_circle Tracks them inside the persistence context
This is raw Hibernate power.
3. Fetching with Pagination
Session session = sessionFactory.openSession();
List<User> users = session
.createQuery("FROM User ORDER BY id", User.class)
.setFirstResult(0) // offset
.setMaxResults(10) // limit
.getResultList();
session.close();Hibernate translates this to:
SELECT * FROM users
ORDER BY id
LIMIT 10 OFFSET 0;You control performance explicitly.
4. Fetching with WHERE Condition
Session session = sessionFactory.openSession();
List<User> users = session
.createQuery(
"FROM User u WHERE u.email LIKE :domain",
User.class
)
.setParameter("domain", "%@gmail.com")
.getResultList();
session.close();This is HQL, not SQL
- check_circle Uses entity name (User)
- check_circle Uses field names (email)
- check_circle Hibernate adapts it to your database dialect
5. Native SQL (When You Really Need Control)
Sometimes abstractions are not enough.
Session session = sessionFactory.openSession();
List<User> users = session
.createNativeQuery(
"SELECT * FROM users WHERE email LIKE ?",
User.class
)
.setParameter(1, "%@gmail.com")
.getResultList();
session.close();Hibernate still maps results → entities, but you own the SQL.
6. The Pain Point (Why Spring Data JPA Exists)
Notice what you had to manage:
- check_circle Opening/closing sessions
- check_circle Transactions
- check_circle Query strings
- check_circle Pagination logic
This is why Spring Data JPA lets you write:
List<User> findByEmailEndingWith(String domain);Spring Data JPA: The Productivity Layer
Now here’s the important part many people misunderstand: Spring Data JPA is NOT an ORM.
It’s a Spring abstraction built on top of JPA, and JPA itself is only a specification. Hibernate is usually the implementation behind it.
With Spring Data JPA, you write this:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}And then:
User user = userRepository.findById(1L).orElseThrow();
user.setName("Joekawai");
userRepository.save(user);
No session.
No transaction boilerplate.
No SQL (most of the time).
Spring handles all of that for you.
The Mental Model That Finally Made It Click
This is the simplest way to understand it:
- check_circle Hibernate → How data is persisted
- check_circle Spring Data JPA → How developers access data
In a typical Spring Boot application, the flow looks like this:
Spring Data JPA
↓
JPA
↓
Hibernate
↓
DatabaseWhen you call repository.save(entity) , Hibernate is the one generating SQL and talking to the database.
If Spring Data JPA Is So Easy, Why Learn Hibernate?
This is the trap many developers fall into — including me.
Spring Data JPA makes you productive fast. But Hibernate behavior still controls everything under the hood.
If you don’t understand Hibernate, you will eventually face:
- check_circle N+1 query problems
- check_circle Lazy loading errors
- check_circle Mysterious performance degradation
- check_circle “Why is this query running 20 times?”
Spring Data JPA hides complexity.
Hibernate explains it.
A junior developer uses Spring Data JPA.
A senior developer understands Hibernate while using Spring Data JPA.
When Should You Use Hibernate Directly?
Most of the time, you don’t need to.
Spring Data JPA is perfect for:
- check_circle CRUD-heavy applications
- check_circle Microservices
- check_circle Most enterprise systems
You may go closer to Hibernate when:
- check_circle You need very fine-grained performance tuning
- check_circle You’re dealing with complex legacy schemas
- check_circle You must control SQL generation precisely
Even then, Spring Data JPA usually stays — you just customize Hibernate behavior.
Final Takeaway
- check_circle Hibernate is the engine that talks to the database
- check_circle Spring Data JPA is the convenience layer that saves your time
- check_circle They don’t compete — they cooperate
- check_circle Mastery comes from knowing both, not choosing one
If this resonates with you, trust me — you’re already on the path from framework user to real backend engineer.