Back to Spring Data Repositories
Q1Question 1 of 4

CrudRepository vs JpaRepository?

Key Concepts
  • CrudRepository provides basic CRUD: save, findById, findAll (returns Iterable), deleteById, count, existsById
  • JpaRepository extends CrudRepository through ListCrudRepository and PagingAndSortingRepository
  • JpaRepository adds: pagination (findAll with Pageable), sorting, flush, saveAndFlush, batch delete, and lazy references
  • JpaRepository returns List instead of Iterable – ready to use without conversion
  • deleteAllInBatch() executes one DELETE SQL; deleteAll() loads all entities first then deletes one by one
  • getReferenceById() returns a lazy proxy without a database hit – efficient for FK-only writes
  • Use JpaRepository for most Spring Boot apps; use CrudRepository only for technology-agnostic code

Definition

CrudRepository provides basic CRUD (save, find, delete, count); JpaRepository extends it with pagination, sorting, flush, batch delete, and lazy references – use JpaRepository for Spring Boot apps.
  • CrudRepository – basic CRUD: save(), findById(), findAll() (returns Iterable), deleteById(), count(), existsById(). Technology-agnostic (works with JPA, MongoDB, Redis).
  • JpaRepository – extends CrudRepository through ListCrudRepository and PagingAndSortingRepository. Adds pagination (findAll(Pageable)), sorting (findAll(Sort)), flush(), saveAndFlush(), deleteAllInBatch(), and getReferenceById().
  • Return types – CrudRepository returns Iterable; JpaRepository returns List – ready to use without conversion.
  • deleteAllInBatch() – executes one DELETE FROM table SQL statement. deleteAll() loads all entities first, then deletes each one individually.
  • Hierarchy – Repository → CrudRepository → ListCrudRepository → PagingAndSortingRepository → JpaRepository.

CrudRepository is like a basic bank counter at National Bank of India – deposit (save), withdraw (delete), check balance (findById), list transactions (findAll). JpaRepository is the premium counter – everything basic plus: sort transactions by date, paginate (show page 5), flush immediately (saveAndFlush), bulk delete 10,000 old records in one shot (deleteAllInBatch), and lazy references for efficient FK writes.

Understanding the Concepts

What is CrudRepository?

CrudRepository is the basic Spring Data interface that provides standard CRUD operations. It is technology-agnostic: the same interface works with JPA, MongoDB, Redis, and other Spring Data modules.

JAVA
package com.bank.repository;

import com.bank.model.Account;
import org.springframework.data.repository.CrudRepository;

public interface AccountCrudRepo extends CrudRepository<Account, String> {
    // Inherited methods:
    // Account save(Account entity)
    // Iterable<Account> saveAll(Iterable<Account> entities)
    // Optional<Account> findById(String id)
    // Iterable<Account> findAll()  -- returns Iterable, not List!
    // Iterable<Account> findAllById(Iterable<String> ids)
    // long count()
    // boolean existsById(String id)
    // void deleteById(String id)
    // void delete(Account entity)
    // void deleteAll()
}

What is JpaRepository?

JpaRepository extends CrudRepository (through several intermediate interfaces) and adds JPA-specific features: pagination, sorting, flush control, batch operations, and lazy references.

JAVA
package com.bank.repository;

import com.bank.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AccountRepository extends JpaRepository<Account, String> {
    // All CrudRepository methods PLUS:
    // List<Account> findAll()  -- returns List, not Iterable!
    // List<Account> findAll(Sort sort)
    // Page<Account> findAll(Pageable pageable)
    // Account saveAndFlush(Account entity)
    // List<Account> saveAllAndFlush(Iterable<Account> entities)
    // void flush()
    // void deleteAllInBatch()
    // void deleteAllByIdInBatch(Iterable<String> ids)
    // Account getReferenceById(String id)  -- lazy proxy, no DB hit
}

What is the repository hierarchy?

Spring Data's repository interfaces form a hierarchy. Each level adds more methods.

DIAGRAM
Repository<T, ID>                     (marker, no methods)
  └─ CrudRepository<T, ID>             (basic CRUD, Iterable returns)
      └─ ListCrudRepository<T, ID>      (same but List returns)
          └─ PagingAndSortingRepository  (findAll with Sort, Pageable)
              └─ JpaRepository<T, ID>    (flush, batch, lazy ref)
JAVA
package com.bank.service;

import com.bank.model.Account;
import com.bank.repository.AccountRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
public class AccountListService {

    private final AccountRepository accountRepo;

    public AccountListService(AccountRepository accountRepo) {
        this.accountRepo = accountRepo;
    }

    @Transactional(readOnly = true)
    public List<Account> findAll() {
        return accountRepo.findAll();
        // JpaRepository: returns List<Account> directly
        // CrudRepository would return: Iterable<Account> (needs conversion!)
    }

    @Transactional(readOnly = true)
    public Page<Account> findPaginated(int page, int size) {
        return accountRepo.findAll(
            PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "balance")));
        // Only available in JpaRepository (via PagingAndSortingRepository)
    }
}

How is deleteAllInBatch different from deleteAll?

deleteAll() loads ALL entities into memory first, then deletes each one individually with separate DELETE statements. deleteAllInBatch() executes a single DELETE FROM table SQL – orders of magnitude faster but skips JPA lifecycle callbacks.

| Feature | deleteAll() | deleteAllInBatch() |
|---------|------------|-------------------|
| SQL | N SELECT + N DELETE | 1 DELETE FROM |
| Speed (10K rows) | ~30 minutes | ~2 seconds |
| Loads entities | Yes | No |
| @PreRemove callback | Fires | Skipped |
| Cascade | Honored | Skipped |

JAVA
package com.bank.service;

import com.bank.repository.AccountRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class CleanupService {

    private final AccountRepository accountRepo;

    public CleanupService(AccountRepository accountRepo) {
        this.accountRepo = accountRepo;
    }

    // WRONG: deleteAll loads all entities then deletes one by one
    @Transactional
    public void wrongCleanup() {
        accountRepo.deleteAll();
        // SELECT * FROM accounts  (loads 10K entities into memory)
        // DELETE FROM accounts WHERE account_id = 'SAV0001'
        // DELETE FROM accounts WHERE account_id = 'SAV0002'
        // ... 10,000 individual DELETE statements
    }

    // CORRECT: deleteAllInBatch uses one SQL statement
    @Transactional
    public void correctCleanup() {
        accountRepo.deleteAllInBatch();
        // DELETE FROM accounts  (one SQL statement, instant)
        // Note: @PreRemove callbacks are NOT triggered
    }
}

What is getReferenceById?

getReferenceById() returns a lazy proxy without hitting the database. It is efficient for setting foreign keys on child entities. This method is available only in JpaRepository.

JAVA
package com.bank.service;

import com.bank.model.Account;
import com.bank.model.Trade;
import com.bank.repository.AccountRepository;
import com.bank.repository.TradeRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class TradePlacementService {

    private final AccountRepository accountRepo;
    private final TradeRepository tradeRepo;

    public TradePlacementService(AccountRepository accountRepo,
                                  TradeRepository tradeRepo) {
        this.accountRepo = accountRepo;
        this.tradeRepo = tradeRepo;
    }

    @Transactional
    public void placeTrade(String accountId, String symbol, BigDecimal amount) {
        // getReferenceById: NO database hit, just a proxy with the ID
        Account accountRef = accountRepo.getReferenceById(accountId);

        Trade trade = new Trade();
        trade.setTradeId("TRD-099");
        trade.setSymbol(symbol);
        trade.setAmount(amount);
        trade.setAccount(accountRef);  // sets FK without loading Account

        tradeRepo.save(trade);
        // INSERT INTO trades (..., account_id) VALUES (..., 'SAV2001')
        // Account was NEVER loaded from the database!
    }
}

What is saveAndFlush?

saveAndFlush() persists the entity and immediately flushes the SQL to the database, instead of waiting until the transaction commits. Useful when you need the generated ID immediately or when you need the SQL to execute for database triggers.

JAVA
package com.bank.service;

import com.bank.model.Account;
import com.bank.repository.AccountRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
public class AccountCreationService {

    private final AccountRepository accountRepo;

    public AccountCreationService(AccountRepository accountRepo) {
        this.accountRepo = accountRepo;
    }

    @Transactional
    public Account createAccount(String id, String name, BigDecimal balance) {
        Account account = new Account();
        account.setAccountId(id);
        account.setHolderName(name);
        account.setBalance(balance);

        // save(): queues INSERT, flushes at commit
        // accountRepo.save(account);

        // saveAndFlush(): INSERT executes immediately
        Account saved = accountRepo.saveAndFlush(account);
        // SQL fires NOW, not at commit time
        // Useful when a DB trigger creates related records
        return saved;
    }
}

When should you use CrudRepository vs JpaRepository?

Use JpaRepository for most Spring Boot applications. Use CrudRepository only if you need technology-agnostic code that could switch between JPA and another Spring Data module.

JAVA
package com.bank.repository;

import com.bank.model.Account;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.jpa.repository.JpaRepository;

// Technology-agnostic: works with JPA, MongoDB, Redis
// Missing: pagination, flush, batch delete, lazy references
public interface AccountCrudRepo extends CrudRepository<Account, String> {}

// JPA-specific: full-featured, recommended for Spring Boot
public interface AccountJpaRepo extends JpaRepository<Account, String> {}

What are common mistakes?

The most common mistake is using CrudRepository and then struggling with Iterable return types or missing pagination.

JAVA
package com.bank.service;

import com.bank.model.Account;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
import java.util.stream.StreamSupport;

public class CrudRepoMistake {

    // WRONG: CrudRepository returns Iterable, not List
    public List<Account> wrongUsage(CrudRepository<Account, String> repo) {
        Iterable<Account> iterable = repo.findAll();
        // Must convert manually:
        return StreamSupport.stream(iterable.spliterator(), false).toList();
    }

    // CORRECT: JpaRepository returns List directly
    public List<Account> correctUsage(
            org.springframework.data.jpa.repository.JpaRepository<Account, String> repo) {
        return repo.findAll();  // List<Account> directly!
    }
}

Visual Overview

DIAGRAM
  Repository Hierarchy:
  ┌───────────────────────────┐
  │  Repository<T, ID>       │  (marker interface)
  └─────────────┬─────────────┘
                │
                ▼
  ┌───────────────────────────┐
  │  CrudRepository          │  save, findById, findAll(Iterable)
  └─────────────┬─────────────┘
                │
                ▼
  ┌───────────────────────────┐
  │  ListCrudRepository      │  findAll(List), findAllById(List)
  └─────────────┬─────────────┘
                │
                ▼
  ┌───────────────────────────┐
  │  PagingAndSortingRepo    │  findAll(Sort), findAll(Pageable)
  └─────────────┬─────────────┘
                │
                ▼
  ┌───────────────────────────┐
  │  JpaRepository           │  flush, saveAndFlush,
  │                           │  deleteAllInBatch,
  │                           │  getReferenceById
  └───────────────────────────┘

Key Takeaway

CrudRepository provides basic CRUD operations with Iterable return types and no pagination or batch features. JpaRepository extends it through several intermediate interfaces and adds everything a Spring Boot application needs: List return types, pagination with Pageable, sorting with Sort, immediate flush with saveAndFlush, efficient bulk deletion with deleteAllInBatch, and lazy proxies with getReferenceById.

At National Bank of India, Rajesh uses JpaRepository for the account service. findAll(PageRequest.of(0, 20, Sort.by(DESC, "balance"))) powers the account listing with server-side paging. deleteAllInBatch() removes 100,000 test records in 2 seconds. CrudRepository's deleteAll() would take 30 minutes because it loads all entities first then deletes one by one.

The hierarchy is: Repository → CrudRepository → ListCrudRepository → PagingAndSortingRepository → JpaRepository. Use JpaRepository for most Spring Boot applications. Use CrudRepository only if your code needs to be technology-agnostic, for example a module that could switch from JPA to MongoDB.

Remember one sentence: JpaRepository is a superset of CrudRepository that adds pagination, batch operations, flush control, and lazy references – always use it for Spring Boot apps.

---

Share
Use ← → arrow keys to navigate