Blog

Spring Data JPA vs Hibernate

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 database

Repositories 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

ConcernHibernateSpring Data JPA
Primary roleORM and Jakarta Persistence providerRepository abstraction over Jakarta Persistence
Typical APIsJPA’s EntityManager or Hibernate’s SessionRepository interfaces such as JpaRepository
Main benefitImplements persistence and exposes provider-specific capabilitiesRemoves repetitive repository code and supports declarative queries
Provider dependencyIs itself a providerRequires a compatible provider underneath
CustomizationStandard JPA operations plus native extensionsDeclared 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

InterfaceCollection resultsWhen to use it
CrudRepositoryfindAll() returns Iterable<T>Basic create, read, update, and delete operations
ListCrudRepositoryfindAll(), findAllById(), and saveAll() return listsBasic CRUD with list-valued collection results
JpaRepositoryList-valued CRUD, sorting, and paging supportJPA 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()           -> void

The 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 @Query for 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 EntityManager or 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.

  1. Start with Spring Data repositories for routine persistence in a Spring application.
  2. Add EntityManager-based code when a handwritten persistence operation is clearer than a repository declaration.
  3. 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 persist and merge based 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

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me