Key Takeaways
- Hibernate is a JPA provider: it implements Jakarta Persistence and performs object-relational mapping.
- Spring Data JPA simplifies data access for JPA implementations by supplying repository implementations and query support.
- You commonly use both together. Spring Boot’s standard JPA starter includes Spring Data JPA and Hibernate.
- Repositories can coexist with direct JPA and Hibernate-specific APIs; complex queries don’t force you to abandon Spring Data JPA.
Spring Data JPA vs Hibernate is a comparison of complementary layers, not interchangeable frameworks. Hibernate implements persistence; Spring Data JPA adds a repository abstraction over a JPA provider such as Hibernate. The practical decision is which API your application code should use for each operation.
What Is Hibernate?
Hibernate ORM is an object-relational mapping framework: it maps Java objects and their relationships to relational database tables. As a JPA provider, Hibernate implements standard persistence operations, manages entity state, and generates SQL. It also offers native APIs and features beyond the persistence standard.
The JPA Specification: Now Jakarta Persistence
Jakarta Persistence, commonly still called JPA, defines the standard API for entity mapping, lifecycle management, and querying. For example, @Entity identifies a persistent entity class, while EntityManager provides operations for working with entities. The specification defines the contract; a provider supplies the implementation.
What Is Spring Data JPA?
Spring Data JPA supplies repository support for Jakarta Persistence. You declare a repository interface, and Spring supplies an implementation for supported operations instead of making you write repetitive data-access code. It delegates persistence work through JPA to the configured provider.
Application services
|
Spring Data JPA repositories
|
Jakarta Persistence API: EntityManager
|
Hibernate or another compatible JPA provider
|
Relational databaseRepositories are an optional entry point into this stack. Your services and custom repository implementations can also use EntityManager directly. Spring Data JPA does not replace the provider, entity mappings, or transaction design.
Spring Data JPA vs Hibernate: The Key Differences
| Concern | Hibernate | Spring Data JPA |
|---|---|---|
| Primary role | ORM and Jakarta Persistence provider | Repository abstraction over Jakarta Persistence |
| Typical APIs | JPA’s EntityManager or Hibernate’s Session | Repository interfaces such as JpaRepository |
| Main benefit | Implements persistence and exposes provider-specific capabilities | Removes repetitive repository code and supports declarative queries |
| Provider dependency | Is itself a provider | Requires a compatible provider underneath |
| Customization | Standard JPA operations plus native extensions | Declared queries, specifications, projections, and custom implementations |
The abstraction changes how you express data access, not whether ORM work happens. A repository still relies on the provider’s entity lifecycle, fetching, and SQL execution.
An AuthorRepository Example
Use a Compatible, Dependency-Managed Stack
These application fragments target Spring Boot 4.x with Spring Data JPA 4.x and Jakarta persistence imports. Start with a Boot project using its parent or dependency-management setup; let Boot manage the compatible dependency versions rather than independently pinning Hibernate. Boot’s JPA starter includes Hibernate, Spring Data JPA, and Spring ORM.
Add these dependencies inside that project’s existing Maven dependencies element. H2 is for the local demonstration, not a production database recommendation.
<!-- Fragment: dependencies in a Spring Boot-managed Maven project. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>Keep the following classes beneath your application’s bookservice root package so Boot’s normal scanning can discover them. The official JPA getting-started guide covers project generation and application bootstrap; our Spring Data JPA quick start provides another walkthrough.
Define the Entity and Repository
// Application file: bookservice/model/Author.java
package bookservice.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String name;
protected Author() {}
public Author(String name) {
this.name = name;
}
public Long getId() { return id; }
public String getName() { return name; }
}// Application file: bookservice/repository/AuthorRepository.java
package bookservice.repository;
import bookservice.model.Author;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface AuthorRepository extends JpaRepository<Author, Long> {
List<Author> findByName(String name);
@Query("select a from Author a where a.name = :name")
List<Author> findNamed(@Param("name") String name);
}findByName is a derived query: Spring Data creates the query from the method name. For non-null names, findNamed declares the equivalent condition explicitly using JPQL, the persistence query language that refers to entities and their attributes. You don’t write an implementation for either method.
Choose CrudRepository, ListCrudRepository, or JpaRepository
| Interface | Collection results | When to use it |
|---|---|---|
| CrudRepository | findAll() returns Iterable<T> | Basic create, read, update, and delete operations |
| ListCrudRepository | findAll(), findAllById(), and saveAll() return lists | Basic CRUD with list-valued collection results |
| JpaRepository | List-valued CRUD, sorting, and paging support | JPA repositories needing methods such as flush() and saveAndFlush() |
JpaRepository includes findAll(Sort) returning a list and findAll(Pageable) returning a page. Neither CrudRepository nor ListCrudRepository alone supplies those paging and sorting methods.
For the AuthorRepository above, common calls have these result types:
save(author) -> Author
saveAll(authors) -> List<Author>
findById(id) -> Optional<Author>
existsById(id) -> boolean
findAll() -> List<Author>
findAllById(ids) -> List<Author>
count() -> long
deleteById(id) -> void
delete(author) -> void
deleteAll() -> voidThe original List<Author> authors = repo.findAll(); pattern is valid with this JpaRepository, but not with an unmodified CrudRepository, whose return type is Iterable<Author>. Our Spring Data JPA CRUD example explores these operations further.
Transactions Belong to Your Use Case
Inherited CRUD methods receive transactional configuration from Spring Data JPA’s implementation: reads are configured as read-only, while other CRUD methods use ordinary transactions. Declared query methods do not receive transaction configuration by default. Define service-level boundaries when several calls must form one unit of work.
// Application file: bookservice/service/AuthorService.java
package bookservice.service;
import bookservice.model.Author;
import bookservice.repository.AuthorRepository;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AuthorService {
private final AuthorRepository repo;
public AuthorService(AuthorRepository repo) {
this.repo = repo;
}
@Transactional
public List<Author> createAndList(String name) {
repo.save(new Author(name));
return repo.findAll();
}
}Call this service through its Spring-managed bean: the save and subsequent read participate in the same transaction. With Spring’s default proxy-based transaction handling, calling an annotated method from another method on the same object does not activate the transaction interceptor.
Repositories reduce transaction boilerplate; they don’t decide which operations must succeed or fail together. The transaction documentation explains repository defaults and service-level boundaries.
Direct JPA Usage Is Not Hibernate-Native Usage
EntityManager and Persistence.createEntityManagerFactory are Jakarta Persistence APIs, even when Hibernate implements them. The following abbreviated Java SE fragment explicitly manages the transaction and resources. It requires a usable resource-local persistence unit named bookservice in META-INF/persistence.xml, with the Author entity mapped and database access configured. A compatible JPA provider is required, but it need not be Hibernate, and explicit provider selection is optional. Schema-generation settings are unnecessary if the required schema already exists.
// One-shot Java SE fragment; persistence-unit configuration is omitted.
import bookservice.model.Author;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import java.util.List;
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("bookservice");
try {
EntityManager em = factory.createEntityManager();
try {
em.getTransaction().begin();
em.persist(new Author("Sample Author"));
List<Author> authors = em.createQuery(
"select a from Author a", Author.class
).getResultList();
em.getTransaction().commit();
} catch (RuntimeException exception) {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
throw exception;
} finally {
em.close();
}
} finally {
factory.close();
}This performs the same insert-and-list operation as the service example, without repository calls. The factory is closed because this is a one-shot example; in a server application, keep the factory for the application’s lifetime rather than creating one per request.
Direct JPA inside Spring does not require this manual lifecycle code. You can inject a transaction-aware EntityManager with @PersistenceContext and retain Spring-managed transaction boundaries. Use that approach in custom Spring components rather than manually beginning transactions on the injected manager.
Hibernate-native access means using APIs such as org.hibernate.Session. When Hibernate is the provider, entityManager.unwrap(org.hibernate.Session.class) exposes the underlying session. Reserve that dependency for an actual Hibernate-specific requirement.
Complex Queries Don’t Require Abandoning Repositories
Choose the query mechanism that expresses the operation clearly:
- Derived methods: concise names for straightforward filters, such as the example’s
findByName. - Declared queries: use
@Queryfor explicit JPQL; native SQL is also supported. - Projections: return interface-based views or data transfer objects instead of always returning complete entities.
- Specifications: compose reusable query predicates through
JpaSpecificationExecutor, useful for optional search filters. - Custom repository fragments: add handwritten implementations using
EntityManageror another suitable data-access API.
Query complexity alone is not a reason to switch to Hibernate-native APIs. Repositories support these extension points, and handwritten persistence code can live alongside generated operations.
Portability and Choosing the Right API
Spring Data JPA can work with providers other than Hibernate, including EclipseLink. However, “uses JPA” does not guarantee a drop-in provider replacement: check the relevant Spring Data and provider versions, configuration requirements, and features your application relies on. Hibernate-specific APIs and database-specific SQL introduce additional coupling.
- Start with Spring Data repositories for routine persistence in a Spring application.
- Add EntityManager-based code when a handwritten persistence operation is clearer than a repository declaration.
- Use Hibernate-specific APIs when you need a feature outside the standard JPA contract, and isolate that dependency.
For a new Spring-based application where an ORM fits the data model, Spring Data JPA with Hibernate is a sensible starting point. You can keep that combination while adding lower-level access selectively.
Common Mistakes
- Ignoring generated SQL. Inspect the queries your repository methods produce and test them with realistic data volumes.
- Assuming repositories prevent N+1 queries. An N+1 pattern loads a set of entities, then issues additional queries for their associations. Plan the required data with fetch joins, entity graphs, or appropriate projections; changing everything to eager fetching is not a general fix.
- Accessing unloaded relationships after the persistence context closes. Lazy loading needs an available persistence context. Fetch the data your use case needs before returning a detached result. Our Spring Data JPA one-to-many example provides relationship-mapping context.
- Treating every save as an immediate SQL insert. Spring Data JPA chooses between
persistandmergebased on entity newness; SQL synchronization and transaction commit remain separate concerns. - Loading entire tables by accident. Unpaged
findAll()returns all entities. Use bounded queries or paging for growing datasets.
Conclusion
Hibernate is the ORM/provider layer; Spring Data JPA is the repository abstraction above JPA. Use repositories to remove repetitive code, direct JPA where explicit persistence logic helps, and Hibernate-native APIs where a provider-specific capability earns the coupling. These are complementary tools, not mutually exclusive choices.
FAQ
What is the difference between JPA, Hibernate, and Spring Data JPA?
Jakarta Persistence, commonly called JPA, defines the persistence standard. Hibernate implements that standard as an ORM/provider, while Spring Data JPA adds repository support on top of a JPA provider.
Does Spring Data JPA use Hibernate by default?
Spring Boot’s standard spring-boot-starter-data-jpa starter includes Hibernate as its provider. Spring Data JPA itself is a repository abstraction and is not restricted to Hibernate.
Can Spring Data JPA work without Hibernate?
Yes. Spring Data JPA can use another compatible JPA provider, such as EclipseLink. Check compatibility for your Spring Data and provider versions, and review any Hibernate-specific features before switching.
When should I use EntityManager or Hibernate APIs instead of a repository?
Use EntityManager when a handwritten persistence operation is clearer than a repository method; it can live inside a custom repository implementation. Use Hibernate-native APIs when you specifically need a Hibernate feature outside the standard JPA contract.
Can Spring Data JPA handle complex queries?
Yes. Spring Data JPA supports declared queries, native SQL, projections, specifications, and custom repository implementations. Complex queries alone do not require switching to Hibernate-native APIs.
Further Reading
- Jakarta Persistence specification — entity mapping, lifecycle, queries, and persistence contexts.
- Spring Boot dependency management — keeping framework and provider dependencies aligned.
- Spring Boot SQL and JPA configuration — starter dependencies, entity scanning, and repositories.
- JpaRepository API — inherited operations and JPA-specific methods.
- Spring Data JPA transactionality — CRUD defaults and application transaction boundaries.
- Spring Data JPA query methods — derived queries, declared queries, and fetch graphs.
- Hibernate introduction — persistence contexts, transactions, and fetching.
- Hibernate ORM user guide — detailed mapping and performance behavior.
