Repositories
A repository in Spring Data JPA is an interface you declare; Spring generates the implementation at runtime. By extending one of the built-in interfaces you instantly get CRUD, paging, and batch operations for an entity without writing any boilerplate. This page walks the repository hierarchy and shows what each interface adds. For the wider data layer, see Spring Data JPA.
The repository hierarchy
Spring Data layers several interfaces, each adding more capability:
Repository<T, ID> (marker, no methods)
└─ CrudRepository<T, ID> (save / findById / findAll / delete / count)
├─ ListCrudRepository<T, ID> (same, but returns List instead of Iterable)
└─ PagingAndSortingRepository (findAll(Sort) / findAll(Pageable))
└─ JpaRepository<T, ID> (saveAll, flush, batch deletes, getReferenceById)
Repositoryis a pure marker interface. It declares no methods and exists so Spring can detect repository beans.CrudRepositoryadds the core operations:save,saveAll,findById(returnsOptional),findAll,existsById,delete,deleteById, andcount. Collection results come back asIterable.ListCrudRepositoryoffers the same methods but returnsListinstead ofIterable, which is friendlier for streams and indexing.PagingAndSortingRepositoryaddsfindAll(Sort sort)andfindAll(Pageable pageable)for ordered and paged access.JpaRepositorycombines everything above and adds JPA-specific methods:flush,saveAndFlush,saveAllAndFlush,deleteAllInBatch,deleteAllByIdInBatch, andgetReferenceById. Its collection methods returnList.
Defining a repository
Extend JpaRepository<EntityType, IdType>. That single line gives you all the inherited methods:
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
// inherits save, findById, findAll, deleteById, and more
// add derived query methods here when you need them
}
No @Repository annotation is required; Spring Data registers the bean automatically.
Using it in a service
Inject the repository with constructor injection (Lombok @RequiredArgsConstructor generates the constructor):
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository repository;
public Product create(String name) {
return repository.save(new Product(name));
}
public Product findById(Long id) {
// findById returns Optional<Product>
return repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("No product " + id));
}
public List<Product> findAll() {
return repository.findAll();
}
public void delete(Long id) {
repository.deleteById(id);
}
}
Notice findById returns an Optional, forcing you to handle the missing case explicitly. See Java Optional for idiomatic handling.
Tip:
saveperforms an insert when the entity is new (null id) and an update (merge) when it already has an id. There is no separateupdatemethod.
Interface comparison
| Interface | Extends | Key methods added | Return style |
|---|---|---|---|
Repository<T, ID> | — | none (marker) | — |
CrudRepository<T, ID> | Repository | save, findById, findAll, deleteById, count, existsById | Iterable, Optional |
ListCrudRepository<T, ID> | CrudRepository | same CRUD methods | List instead of Iterable |
PagingAndSortingRepository<T, ID> | Repository | findAll(Sort), findAll(Pageable) | Iterable, Page |
JpaRepository<T, ID> | ListCrudRepository + ListPagingAndSortingRepository | flush, saveAndFlush, deleteAllInBatch, getReferenceById | List |
getReferenceById and lazy proxies
getReferenceById (formerly getOne) does not hit the database immediately. It returns a lazy proxy with the id populated; the actual SELECT fires only when you access another field.
public void linkCategory(Long productId, Long categoryId) {
// no SELECT yet, just a proxy reference
Category category = categoryRepository.getReferenceById(categoryId);
Product product = productRepository.findById(productId).orElseThrow();
product.setCategory(category); // sets the FK without loading the Category
}
Warning: Accessing a
getReferenceByIdproxy outside an active transaction throwsLazyInitializationException. Only use it when you merely need the foreign key, and within a transaction.
Batch helpers
JpaRepository adds methods that bypass per-row processing:
// issues a single bulk DELETE rather than one per row
repository.deleteAllInBatch();
// flush pending changes to the DB within the current transaction
repository.saveAndFlush(product);
Note:
deleteAllInBatchskips entity-lifecycle callbacks and cascade logic because it runs a bulk SQL statement. Use it for fast cleanup where those side effects don’t matter.
Next steps
With a repository in place, you can declare query methods by naming convention or write explicit queries:
- Derived query methods — methods like
findByNameparsed from the method name. - JPQL & native queries —
@Queryfor custom SQL/JPQL. - Pagination with JPA —
PageandPageableresults.