How to Implement Repository Pattern for Data Access Abstraction in Java

The Repository pattern isolates domain logic from persistence details by exposing a collection-like interface for CRUD operations, typically implemented via Spring Data JPA to auto-generate data access implementations at runtime.

The Repository pattern is essential for building maintainable Java applications that require clean separation between business logic and data storage mechanisms. In the iluwatar/java-design-patterns repository, this pattern is demonstrated through a practical implementation using Spring Data JPA, showcasing how to abstract database operations behind a simple interface contract.

Architecture of the Repository Pattern Implementation

The implementation in iluwatar/java-design-patterns consists of four tightly integrated components that demonstrate production-ready data access abstraction. Each component resides in the repository/src/main/java/com/iluwatar/repository/ directory.

Domain Entity Definition

The Person class represents the core data model as a JPA entity. Located at repository/src/main/java/com/iluwatar/repository/Person.java, this POJO maps directly to a database table while remaining completely unaware of repository implementation details.

@Entity
public class Person {
  @Id @GeneratedValue private Long id;
  private String name;
  private String surname;
  private int age;

  public Person(String name, String surname, int age) {
    this.name = name;
    this.surname = surname;
    this.age = age;
  }
}

This entity uses standard JPA annotations (@Entity, @Id, @GeneratedValue) to define the primary key and persistence metadata, allowing the repository layer to manage its lifecycle without explicit SQL.

Repository Interface Contract

The PersonRepository interface at repository/src/main/java/com/iluwatar/repository/PersonRepository.java defines the API contract for data access. By extending Spring Data interfaces, you eliminate the need to write boilerplate data access code.

@Repository
public interface PersonRepository
    extends CrudRepository<Person, Long>, JpaSpecificationExecutor<Person> {

  Person findByName(String name);
}

Key interface extensions:

  • CrudRepository<Person, Long> – Provides standard operations including save(), findAll(), findById(), and deleteById().
  • JpaSpecificationExecutor<Person> – Enables type-safe, composable query specifications using the Criteria API.

Spring automatically generates the concrete implementation at runtime, injecting the necessary EntityManager and transaction management.

Specification Pattern for Query Abstraction

Complex query logic is encapsulated in repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java using the Specification pattern. This approach keeps query criteria out of the service layer and promotes reuse across the application.

public class PersonSpecifications {

  public static class AgeBetweenSpec implements Specification<Person> {
    private final int from;
    private final int to;

    public AgeBetweenSpec(int from, int to) { 
      this.from = from; 
      this.to = to; 
    }

    @Override
    public Predicate toPredicate(Root<Person> root,
                                 CriteriaQuery<?> query,
                                 CriteriaBuilder cb) {
      return cb.between(root.get("age"), from, to);
    }
  }

  public static class NameEqualSpec implements Specification<Person> {
    private final String name;
    
    public NameEqualSpec(String name) { 
      this.name = name; 
    }

    @Override
    public Predicate toPredicate(Root<Person> root,
                                 CriteriaQuery<?> query,
                                 CriteriaBuilder cb) {
      return cb.equal(root.get("name"), name);
    }
  }
}

Each specification implements toPredicate() to define criteria using JPA's CriteriaBuilder, ensuring compile-time safety and database portability.

Application Configuration and Bootstrapping

The App.java class at repository/src/main/java/com/iluwatar/repository/App.java demonstrates end-to-end usage. It loads Spring's application context from repository/src/main/resources/applicationContext.xml, which configures an in-memory H2 datasource and enables component scanning for repository beans.

var context = new ClassPathXmlApplicationContext("applicationContext.xml");
var repo = context.getBean(PersonRepository.class);

// Create
repo.save(new Person("Alice", "Smith", 30));
repo.save(new Person("Bob", "Jones", 45));

// Read
repo.findById(1L).ifPresent(p -> System.out.println(p));

// Update
repo.findByName("Alice").setAge(31);
repo.save(repo.findByName("Alice"));

// Delete
repo.deleteById(2L);

// Specification-based query
List<Person> youngAdults = repo.findAll(
    new PersonSpecifications.AgeBetweenSpec(20, 35));
youngAdults.forEach(System.out::println);

This bootstrap code illustrates how the repository provides a collection-like API that hides the underlying JPA EntityManager and transaction boundaries.

Benefits of This Repository Pattern Implementation

Implementing the Repository pattern as shown in iluwatar/java-design-patterns delivers three critical architectural advantages:

  1. Decoupling – Business code depends only on the PersonRepository interface, not on JPA, Hibernate, or SQL specifics. You can switch from H2 to PostgreSQL without modifying domain logic.
  2. Testability – Mock implementations of PersonRepository can replace the real bean in unit tests, allowing you to test service layers without requiring a live database.
  3. Extensibility – New query requirements are added as new Specification classes without modifying existing repository interfaces or service code, adhering to the Open/Closed Principle.

Summary

  • The Repository pattern creates a clean abstraction layer between domain models and data persistence technologies.
  • Spring Data JPA eliminates boilerplate by auto-implementing repository interfaces that extend CrudRepository and JpaSpecificationExecutor.
  • The Specification pattern (as seen in PersonSpecifications.java) encapsulates reusable query logic using JPA Criteria API for type-safe, composable queries.
  • Domain entities like Person.java remain persistence-ignorant POJOs with simple JPA annotations.
  • Configuration via applicationContext.xml wires the datasource and enables repository scanning without explicit bean definitions.

Frequently Asked Questions

What is the difference between Repository pattern and DAO pattern?

While both abstract data access, the DAO (Data Access Object) pattern typically maps one-to-one with database tables and focuses on data persistence mechanics. The Repository pattern operates at a higher abstraction level, treating the data access layer as a collection of domain objects and speaking the ubiquitous language of the domain. In iluwatar/java-design-patterns, the PersonRepository exposes methods like findByName() that reflect business concepts rather than database operations.

Does Spring Data JPA require writing SQL for the Repository pattern?

No. Spring Data JPA derives queries from method names (like findByName) and provides the JpaSpecificationExecutor interface for programmatic criteria construction. As demonstrated in PersonSpecifications.java, you build queries using the Criteria API (CriteriaBuilder, Predicate) rather than raw SQL, though you can use @Query annotations for complex native SQL when necessary.

How do you test code that uses the Repository pattern?

You can test service layers by mocking the repository interface using frameworks like Mockito. Since PersonRepository is an interface extending CrudRepository, you inject mock implementations in unit tests to verify business logic without database dependencies. For integration tests, Spring provides @DataJpaTest which configures an embedded database and repository layer automatically.

Can the Repository pattern be used without Spring Framework?

Yes. The Repository pattern is framework-agnostic. You can implement it using plain JDBC, JPA's EntityManager directly, or other frameworks like Quarkus or Micronaut. The iluwatar/java-design-patterns implementation uses Spring for convenience, but the core concept—hiding persistence details behind a collection-like interface—applies regardless of the underlying technology stack.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →