Spring Data JPA

Master Spring Data JPA — the abstraction layer over Hibernate that eliminates boilerplate data access code through repository interfaces and derived query methods.

Last reviewed: January 2025

·Spring Boot 2.7Spring Boot 3.xHibernate 6
✅
Quick Understanding: Spring Data JPA lets you write zero-SQL data access code for standard operations. Define an interface extending JpaRepository<Entity, ID>, and Spring generates the implementation at startup. You get save(), findById(), findAll(), delete() and more for free. For custom queries, write method names like findByEmailAndStatus() and Spring generates the SQL.

🔍 What Is It?

Spring Data JPA is a layer on top of JPA (Hibernate) that eliminates boilerplate repository code.

What you get for free by extending JpaRepository:

  • →save(entity) — insert or update
  • →findById(id) — returns Optional<T>
  • →findAll() — all entities
  • →findAll(Pageable) — paginated results
  • →findAll(Sort) — sorted results
  • →count() — total count
  • →existsById(id) — existence check
  • →deleteById(id) — delete by ID
  • →saveAll(entities) — batch save

Query generation strategies:

  1. Derived queries — method names: findByEmailAndStatus()
  2. @Query — JPQL or native SQL annotation
  3. Specifications — dynamic queries via Criteria API
  4. QueryDSL — type-safe dynamic queries

🎯 Why Do We Need It?

Without Spring Data JPA, every repository requires:

@Repository
public class UserRepositoryImpl {
    @PersistenceContext
    private EntityManager em;

    public Optional<User> findById(Long id) {
        return Optional.ofNullable(em.find(User.class, id));
    }
    public User save(User user) {
        if (user.getId() == null) em.persist(user);
        else em.merge(user);
        return user;
    }
    // 20 more methods...
}

With Spring Data JPA:

public interface UserRepository extends JpaRepository<User, Long> {
    // That's it. save, findById, findAll, delete all generated.
}

🏗️ What Happens in Real Projects?

In enterprise Spring Boot apps, every entity has a repository interface. The team writes:

  • →Derived query methods for simple filters
  • →@Query for complex JPQL queries
  • →Specifications for dynamic search forms
  • →Custom repository implementations for very complex queries

The auto-generated CRUD handles 80% of data access; custom queries handle the rest.

⚙️ How It Works

At application startup, Spring Data scans for interfaces extending Repository. For each one, it:

  1. Creates a JDK proxy implementing the interface
  2. Parses method names into query ASTs (findByEmailAndStatus → WHERE email=? AND status=?)
  3. Compiles @Query annotations to JPQL/SQL
  4. Registers the proxy as a Spring bean

At runtime, method calls are intercepted by the proxy and delegated to Hibernate.

💻 Example

Spring Data JPA in Practice — From basic repository to complex queries.

java
public interface OrderRepository extends JpaRepository<Order, Long> {

    // Derived query: WHERE customer_id = ? AND status = ?
    List<Order> findByCustomerIdAndStatus(Long customerId, OrderStatus status);

    // Derived query with pagination
    Page<Order> findByCustomerId(Long customerId, Pageable pageable);

    // Derived query: WHERE created_at BETWEEN ? AND ?
    List<Order> findByCreatedAtBetween(LocalDateTime from, LocalDateTime to);

    // JPQL query
    @Query("SELECT o FROM Order o WHERE o.totalAmount > :minAmount " +
           "AND o.status = :status ORDER BY o.createdAt DESC")
    List<Order> findHighValueOrders(
        @Param("minAmount") BigDecimal minAmount,
        @Param("status") OrderStatus status
    );

    // Native SQL query
    @Query(value = "SELECT * FROM orders WHERE DATE(created_at) = CURRENT_DATE",
           nativeQuery = true)
    List<Order> findTodaysOrders();

    // Projection — only fetch needed fields
    @Query("SELECT new com.company.dto.OrderSummary(o.id, o.orderNumber, o.totalAmount) " +
           "FROM Order o WHERE o.customerId = :customerId")
    List<OrderSummary> findOrderSummariesByCustomer(@Param("customerId") Long customerId);

    // Modifying query — UPDATE or DELETE
    @Modifying
    @Transactional
    @Query("UPDATE Order o SET o.status = :status WHERE o.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") OrderStatus status);

    // Exists check
    boolean existsByOrderNumberAndCustomerId(String orderNumber, Long customerId);

    // Count
    long countByStatusAndCreatedAtAfter(OrderStatus status, LocalDateTime since);
}

🤔 Common Confusions

❓ What is the difference between JpaRepository, CrudRepository, and PagingAndSortingRepository?

CrudRepository provides basic CRUD. PagingAndSortingRepository adds findAll(Pageable) and findAll(Sort). JpaRepository extends both and adds JPA-specific methods (flush, saveAndFlush, deleteInBatch). Always extend JpaRepository — it gives you everything.

❓ Why does findAll() cause performance issues?

findAll() loads ALL entities into memory. For a table with 1 million rows, this is catastrophic. Always use findAll(Pageable) for list endpoints. Add @Query with LIMIT for bounded queries. Never call findAll() in production without a size constraint.

❓ What is the N+1 query problem?

If an Order has 10 Items and you load 100 orders, JPA may execute 1 query for orders + 100 queries for items = 101 queries. Fix with JOIN FETCH in @Query: @Query("SELECT o FROM Order o JOIN FETCH o.items") or use @EntityGraph to specify eager loading per query.

🚨 Common Production Issues

N+1 query problem with lazy-loaded relationships — use JOIN FETCH or @EntityGraph

findAll() without pagination loading millions of records into memory

@Modifying without @Transactional — update queries require an active transaction

Open Session in View anti-pattern — disable with spring.jpa.open-in-view=false

Missing index on frequently queried columns causing full table scans

✅ Best Practices

Always disable open-in-view: spring.jpa.open-in-view=false

Use Pageable for all list queries — never unbounded findAll()

Use projections (interface or DTO) to fetch only needed columns

Use @EntityGraph for specific queries that need eager loading

Add @Transactional(readOnly=true) on read-only service methods for performance

Use @Modifying + @Transactional for bulk UPDATE/DELETE queries

🏛️ Architect's Note

Senior Architect's Perspective

Spring Data JPA is excellent for standard CRUD but can hide performance problems. Every derived query method generates SQL — verify it with spring.jpa.show-sql=true in development. For complex reports, aggregations, or bulk operations, consider native SQL queries or even a separate read model. The repository pattern should be thin — complex business logic belongs in the service layer, not in @Query annotations.

💬 Frequently Asked Questions

How do I handle soft deletes (mark as deleted without removing)?

Add a deletedAt field and annotate the entity with @SQLDelete (overrides DELETE SQL) and @Where (filters out soft-deleted records). Or use @Filter for per-query control. Spring Data JPA Soft Delete is also available as a library. Alternatively, manage it manually in the service layer for full control.

What is the difference between @Query JPQL and native SQL?

JPQL operates on entities and is database-independent. Native SQL operates on tables and is database-specific. Use JPQL for portability; use nativeQuery=true for database-specific features (window functions, CTEs, full-text search). For complex analytics, native SQL is often clearer and more performant.

🎯 Key Takeaways

1JpaRepository provides save, findById, findAll, delete and more for free
2Derived query methods (findByEmailAndStatus) generate SQL from method names
3@Query supports JPQL and native SQL for custom queries
4JpaSpecificationExecutor enables dynamic queries without string concatenation
5Always paginate list queries — never call unbounded findAll()
6Disable open-in-view and fix N+1 problems with JOIN FETCH or @EntityGraph

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these Spring Boot references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide Spring Boot learning path

Browse the full Spring Boot topic collection and learning order on SplashRide.

Spring Boot Interview Prep

Practice Spring Boot interview questions and scenario-based answers.

Backend Engineer Career Path

A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.

Full Stack Java Developer Career Path

A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.

Entity Mapping

Master JPA entity mapping - @Entity, @Table, relationships, fetch strategy, and the patterns that prevent common ORM performance pitfalls.

Repositories

Master the Spring Data repository pattern — from basic JpaRepository to custom implementations, projections, and the repository design best practices for enterprise applications.