Foundation · Build

Spring Data Repositories

Extending JpaRepository yields CRUD, pagination, and flushing with no implementation class—Spring Data backs the interface with a proxy at startup.

Primary source: Spring Data JPA: Repositories Interface

15 min

Lesson 5 of 17

1d streak

The Architectural Question

Hand-rolling row mappers for save, find, and delete reimplements solved problems and invites the exact SQL-injection and mapping mistakes the framework prevents.

What this guide proves

  • JpaRepository
  • CrudRepository
  • PagingAndSortingRepository

How Spring Data synthesizes repository implementations at startup

When you declare an interface extending JpaRepository<Order, UUID>, you write zero implementation code. At application startup, Spring Data’s JpaRepositoryFactory uses JDK dynamic proxies to back your interface with SimpleJpaRepository.

  1. 01Spring scans for interfaces extending Repository, CrudRepository, or JpaRepository.
  2. 02JpaRepositoryFactoryBean constructs a ProxyFactory for the interface.
  3. 03Common methods (findById, save, delete) route directly to SimpleJpaRepository.
  4. 04Custom method names (findByStatusAndCreatedAtAfter) are analyzed by PartTree parser.
  5. 05Queries are validated against the entity metadata during startup, failing fast on invalid property names.

The save() trap: persist() vs merge()

Inside SimpleJpaRepository.save(), Spring checks entityInformation.isNew(entity). If true, it calls entityManager.persist(). If false, it calls entityManager.merge(). Calling merge() on a new entity with an assigned ID triggers an unnecessary SELECT query before the INSERT!

Persistable interface

If you use manually assigned IDs (e.g. UUIDs), implement Persistable<UUID> on the entity to tell Spring when an object is genuinely new, avoiding the redundant SELECT query on every creation.

Active Recall Checkpoint

What does entityManager.merge() return, and why must you use its return value?

Hint: Does merge() mutate the original object in place?

Show answer & explanation

entityManager.merge(entity) returns a NEW managed instance attached to the persistence context. The original entity passed as an argument remains unmanaged/detached. You must capture and use the returned instance.

Repository interface rules

Use it when

Extend JpaRepository when you need pagination, sorting, batch operations, and flushing capabilities.

Reconsider when

Extend CrudRepository or Repository when you want to expose only a minimal, restricted subset of data access methods.

Working Implementation

Code Walkthrough

Create a `ProductRepository` interface extending `JpaRepository` and inject it into a service to save and retrieve entities.

ProductRepository.java
public interface ProductRepository
        extends JpaRepository<Product, UUID>,
                        JpaSpecificationExecutor<Product> {
}

@Service
class ProductService {
    private final ProductRepository products;

    ProductService(ProductRepository products) {
        this.products = products;
    }

    @Transactional
    Product create(Product product) {
        return products.save(product);
    }

    @Transactional(readOnly = true)
    Product require(UUID id) {
        return products.findById(id).orElseThrow(ProductNotFound::new);
    }
}
JpaRepository adds the JPA-specific operations on top of CrudRepository and PagingAndSortingRepository: flush, saveAndFlush, deleteAllInBatch, and typed paging.
@SpringBootApplication turns on repository scanning under its own package, and Spring Data builds the implementation proxy at startup.
The transaction boundary sits on the service method, so one use case is one commit no matter how the controller is written.

Synthesis & Production Verification

Deliberate Failure Condition

  1. Break: Scan repositories in a custom setup without @EnableJpaRepositories
  2. Observe: NoSuchBeanDefinitionException for the repository interface
  3. Repair: Boot auto-configures JPA repository scanning; non-standard application layouts must opt back in explicitly.

Production Acceptance Criteria

  • Create ProductRepository extends JpaRepository<Product, Long>
  • Save, findById, and delete through the interface from a service
  • Explain what Spring Data generates at startup versus what stays an interface

Architectural & Technical Review

Core Interview Questions

Q01How does Spring Data implement repository interfaces at runtime?Show answer

JpaRepositoryFactoryBean uses JDK dynamic proxies to delegate standard operations to SimpleJpaRepository and parse query methods dynamically.

Q02Why can calling save() on an entity with an assigned ID trigger an unnecessary SELECT?Show answer

SimpleJpaRepository defaults to isNew() checking ID presence; if non-null, it invokes merge() instead of persist(), firing a SELECT first.

Q03How does Persistable<ID> optimize entity insertions?Show answer

Implementing Persistable allows entities to explicitly declare whether they are new, forcing persist() without preliminary SELECT checks.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Calling saveAndFlush in high-frequency loops — Premature flushing disables Hibernate statement batching and forces immediate database round-trips.
  • Pitfall: Extending JpaRepository everywhere without necessity — Exposing unnecessary repository operations like deleteAll() increases the surface area for accidental data destruction.

Primary Documentation & Specifications