Home/Learn/Hibernate & JPA/Spring Data JPA — JpaRepository

Spring Data JPA — JpaRepository

Beginner
Spring Data JPA

Extend JpaRepository<Entity, ID> to get findById, findAll, save, delete, and pagination out of the box; Spring Data generates the implementation at runtime.

Overview

Spring Data JPA eliminates boilerplate repository code by generating implementations at runtime from interface definitions. Extending `JpaRepository<T, ID>` gives you a full CRUD API plus batch operations and sorting/pagination via `PagingAndSortingRepository`. Spring Data parses **derived query method names** (`findByEmailAndStatus`) into JPQL without any implementation code. For complex queries, `@Query` accepts JPQL or native SQL. `@Modifying` + `@Transactional` turns a query into a bulk UPDATE/DELETE. Projections (interface-based or DTO constructor) reduce the columns fetched from the database.

JpaRepository Basics & Derived Queries

Spring Data inspects the method name, parses the entity's fields, and generates the corresponding JPQL at application startup. Common keywords: `findBy`, `countBy`, `existsBy`, `deleteBy`; connectors: `And`, `Or`; constraints: `Containing`, `StartingWith`, `Between`, `LessThan`, `OrderBy`. No SQL or implementation code required.

Spring Data JPA — derived query methods
@Entity
public class User {
    @Id @GeneratedValue Long id;
    String email;
    String status;
    int age;
}

public interface UserRepository extends JpaRepository<User, Long> {

    // SELECT u FROM User u WHERE u.email = ?1
    Optional<User> findByEmail(String email);

    // SELECT u FROM User u WHERE u.status = ?1 AND u.age >= ?2
    List<User> findByStatusAndAgeGreaterThanEqual(String status, int minAge);

    // SELECT COUNT(u) FROM User u WHERE u.status = ?1
    long countByStatus(String status);

    // SELECT u FROM User u WHERE u.email LIKE %?1%
    List<User> findByEmailContaining(String fragment);

    // Pagination: returns a Page<User> with total count
    Page<User> findByStatus(String status, Pageable pageable);
}

// Usage
Page<User> page = userRepo.findByStatus("ACTIVE",
    PageRequest.of(0, 20, Sort.by("email")));

@Query, @Modifying, and Native Queries

When derived query names become unwieldy, use `@Query` with JPQL (entity names, not table names). Add `nativeQuery = true` for SQL when you need database-specific features. `@Modifying` is required for UPDATE/DELETE queries; always pair it with `@Transactional`.

Spring Data JPA — @Query and @Modifying
public interface UserRepository extends JpaRepository<User, Long> {

    // JPQL — entity fields, not column names
    @Query("SELECT u FROM User u WHERE u.email = :email AND u.status = 'ACTIVE'")
    Optional<User> findActiveByEmail(@Param("email") String email);

    // Native SQL — table/column names
    @Query(value = "SELECT * FROM users WHERE created_at > :since LIMIT :n",
           nativeQuery = true)
    List<User> findRecentNative(@Param("since") LocalDateTime since,
                                @Param("n") int n);

    // Bulk update — must be @Modifying + @Transactional
    @Modifying
    @Transactional
    @Query("UPDATE User u SET u.status = :status WHERE u.age < :age")
    int deactivateByAge(@Param("age") int age, @Param("status") String status);
}

Projections — Reduce Fetched Columns

Fetching full entities when you only need two columns wastes memory and increases query time. Spring Data supports **interface projections** (proxy-based) and **DTO projections** (constructor expression). Interface projections are declarative; DTO projections give you a real class with business logic.

Spring Data JPA — interface and DTO projections
// Interface projection — Spring creates a proxy at runtime
public interface UserSummary {
    String getEmail();
    String getStatus();
}

List<UserSummary> findSummaryByStatus(String status);

// DTO projection — use SELECT new in @Query
public record UserDto(String email, int age) {}

@Query("SELECT new com.example.UserDto(u.email, u.age) FROM User u WHERE u.status = :s")
List<UserDto> findDtoByStatus(@Param("s") String status);

// Dynamic projection — caller chooses the type
<T> List<T> findByStatus(String status, Class<T> type);
// Usage:
List<UserSummary> summaries = repo.findByStatus("ACTIVE", UserSummary.class);
List<User>        full      = repo.findByStatus("ACTIVE", User.class);

Key Points to Remember

  • 1JpaRepository provides CRUD, batch, and pagination APIs without any implementation code
  • 2Derived query method names are parsed into JPQL at startup — no SQL needed for simple queries
  • 3@Query accepts JPQL (entity/field names) or native SQL (nativeQuery=true)
  • 4@Modifying + @Transactional is required for UPDATE/DELETE @Query methods
  • 5Interface projections reduce fetched columns without writing DTO constructors
  • 6Pagination: return Page<T> and pass a Pageable parameter for count + data in one query

Interview Questions

Sign in to ask Aria
1

When would you choose @Query over a derived query method name?

EasyWipro
2

What is the difference between interface projections and DTO projections in Spring Data JPA?

MediumCapgemini
3

Why does a @Modifying query also require @Transactional?

MediumInfosys
4

What is the difference between findById() returning Optional and getOne()/getReferenceById() returning a proxy?

HardThoughtWorks
5

How does Spring Data JPA validate derived query method names?

MediumOracle

Ask Aria about Spring Data JPA — JpaRepository

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…