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
Lesson 5 of 17
1d streakThe 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.
- 01Spring scans for interfaces extending Repository, CrudRepository, or JpaRepository.
- 02JpaRepositoryFactoryBean constructs a ProxyFactory for the interface.
- 03Common methods (findById, save, delete) route directly to SimpleJpaRepository.
- 04Custom method names (findByStatusAndCreatedAtAfter) are analyzed by PartTree parser.
- 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
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.
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);
}
}Synthesis & Production Verification
Deliberate Failure Condition
- Break: Scan repositories in a custom setup without @EnableJpaRepositories
- Observe: NoSuchBeanDefinitionException for the repository interface
- 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
Key Takeaway
Spring Data generates repository proxies at startup backed by SimpleJpaRepository; understand save() behavior to avoid redundant queries.
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
Lesson Primary References
Supplementary Module Resources