Repository Abstractions

About

In a typical Java application, if we want to interact with a database (e.g., fetch, insert, update records), we would traditionally:

  • Write a DAO (Data Access Object) class manually

  • Inject an EntityManager

  • Manually create queries and handle exceptions

Spring Data JPA abstracts all that by providing a Repository Layer.

Repository Abstraction means:

Hiding the complexity of data access layers through ready-made interfaces, while still providing flexibility for customization.

Instead of writing SQL/JPQL and managing EntityManager code manually, we just define interfaces - Spring Data JPA takes care of the rest.

Evolution of Repository Abstractions

Level
Description

DAO Pattern

You create classes to manage database operations manually (classic way).

JPA EntityManager

Java EE way to persist entities with EntityManager.

Spring Data Repositories

Modern abstraction: You only define interfaces; implementation is created automatically.

Spring Data JPA builds a high-level abstraction on top of JPA and EntityManager.

Core Repository Interfaces in Spring Data

Spring Data JPA provides several base interfaces that we can extend:

Interface
Purpose

Repository<T, ID>

Base marker interface — No methods, just identifies a repository.

CrudRepository<T, ID>

Provides basic CRUD methods like save, findById, findAll, delete.

PagingAndSortingRepository<T, ID>

Extends CrudRepository and adds pagination and sorting capabilities.

JpaRepository<T, ID>

Extends PagingAndSortingRepository; adds JPA-specific methods like flush(), saveAll(), deleteInBatch().

QueryByExampleExecutor<T>

Allows query creation using Example (probe object) for dynamic search.

1. Repository<T, ID>

  • Pure marker interface — does not declare any method.

  • Purpose: Identify the type as a Repository for Spring Data to pick up.

public interface MyRepository extends Repository<Employee, Long> { }

2. CrudRepository<T, ID>

  • Provides basic CRUD operations:

Method
Description

save(S entity)

Save an entity.

findById(ID id)

Find an entity by its ID.

findAll()

Fetch all entities.

delete(T entity)

Delete an entity.

existsById(ID id)

Check if entity exists.

count()

Get count of entities.

public interface EmployeeRepository extends CrudRepository<Employee, Long> { }

3. PagingAndSortingRepository<T, ID>

  • Extends CrudRepository.

  • Adds pagination and sorting capabilities.

Method
Description

findAll(Pageable pageable)

Fetch paginated data.

findAll(Sort sort)

Fetch sorted data.

Page<Employee> findAll(Pageable pageable);
List<Employee> findAll(Sort sort);

4. JpaRepository<T, ID>

  • Extends PagingAndSortingRepository.

  • Adds JPA-specific optimizations like:

Method
Description

flush()

Synchronizes persistence context to the database immediately.

saveAll(Iterable<S> entities)

Batch save entities.

deleteInBatch(Iterable<T> entities)

Batch delete.

findAllById(Iterable<ID> ids)

Find all entities matching IDs.

public interface EmployeeRepository extends JpaRepository<Employee, Long> { }

5. QueryByExampleExecutor<T>

  • Allows building queries using Example Matching instead of manual query methods.

Example:

Employee probe = new Employee();
probe.setDepartment("IT");
Example<Employee> example = Example.of(probe);

List<Employee> employees = employeeRepository.findAll(example);

No need to define explicit query methods.

How Repository Abstractions Work Internally ?

  • Spring Data JPA creates dynamic proxies for the repository interfaces.

  • Proxies interpret method names to generate SQL/JPQL automatically.

  • If we add @Query, it directly executes the provided query.

  • The proxy uses injected EntityManager to perform all operations.

No manual implementation needed. No manual query writing needed for simple cases.

Customizing Repositories

If default behavior isn't enough:

  • We can write custom methods manually (Custom Repository Implementation).

  • Or override default methods.

public interface CustomEmployeeRepository {
    List<Employee> findEmployeesWithHighSalary();
}

public class CustomEmployeeRepositoryImpl implements CustomEmployeeRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<Employee> findEmployeesWithHighSalary() {
        // custom JPQL
        return entityManager.createQuery("SELECT e FROM Employee e WHERE e.salary > 100000", Employee.class)
                .getResultList();
    }
}

Then:

public interface EmployeeRepository extends JpaRepository<Employee, Long>, CustomEmployeeRepository { }

Last updated

Was this helpful?