Skip to content
Spring Boot sb data-jpa 3 min read

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)
  • Repository is a pure marker interface. It declares no methods and exists so Spring can detect repository beans.
  • CrudRepository adds the core operations: save, saveAll, findById (returns Optional), findAll, existsById, delete, deleteById, and count. Collection results come back as Iterable.
  • ListCrudRepository offers the same methods but returns List instead of Iterable, which is friendlier for streams and indexing.
  • PagingAndSortingRepository adds findAll(Sort sort) and findAll(Pageable pageable) for ordered and paged access.
  • JpaRepository combines everything above and adds JPA-specific methods: flush, saveAndFlush, saveAllAndFlush, deleteAllInBatch, deleteAllByIdInBatch, and getReferenceById. Its collection methods return List.

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: save performs an insert when the entity is new (null id) and an update (merge) when it already has an id. There is no separate update method.

Interface comparison

InterfaceExtendsKey methods addedReturn style
Repository<T, ID>none (marker)
CrudRepository<T, ID>Repositorysave, findById, findAll, deleteById, count, existsByIdIterable, Optional
ListCrudRepository<T, ID>CrudRepositorysame CRUD methodsList instead of Iterable
PagingAndSortingRepository<T, ID>RepositoryfindAll(Sort), findAll(Pageable)Iterable, Page
JpaRepository<T, ID>ListCrudRepository + ListPagingAndSortingRepositoryflush, saveAndFlush, deleteAllInBatch, getReferenceByIdList

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 getReferenceById proxy outside an active transaction throws LazyInitializationException. 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: deleteAllInBatch skips 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:

Last updated June 13, 2026
Was this helpful?