Spring Data JPA Repository
Complete guide to Spring Data JPA Repository with JpaRepository, CrudRepository, query methods, JPQL, native queries, pagination, sorting, projections, specifications, examples, diagrams, and interview questions.
What is Spring Data JPA Repository?
Spring Data JPA Repository is an abstraction that reduces boilerplate database code.
Instead of writing DAO code manually:
entityManager.persist(employee);
entityManager.find(Employee.class, id);
entityManager.remove(employee);
Spring Data JPA allows:
employeeRepository.save(employee);
employeeRepository.findById(id);
employeeRepository.deleteById(id);
Simple meaning:
Repository = Interface that provides database operations
Why Repository is Important
Without Spring Data JPA:
@Repository
public class EmployeeDao {
@PersistenceContext
private EntityManager entityManager;
public void save(Employee employee) {
entityManager.persist(employee);
}
public Employee findById(Long id) {
return entityManager.find(Employee.class, id);
}
public void delete(Employee employee) {
entityManager.remove(employee);
}
}
With Spring Data JPA:
public interface EmployeeRepository
extends JpaRepository<Employee, Long> {
}
Spring automatically creates implementation at runtime.
High Level Architecture
flowchart TD
A["Controller"]
B["Service"]
C["Repository Interface"]
D["Spring Data Proxy Implementation"]
E["EntityManager"]
F["Hibernate"]
G["Database"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
Repository Hierarchy
flowchart TD
A["Repository"]
B["CrudRepository"]
C["PagingAndSortingRepository"]
D["JpaRepository"]
A --> B
B --> C
C --> D
Main Repository Types
| Repository | Purpose |
|---|---|
| Repository | Marker interface |
| CrudRepository | Basic CRUD |
| PagingAndSortingRepository | CRUD plus pagination and sorting |
| JpaRepository | Full JPA repository features |
CrudRepository
Provides basic CRUD methods.
public interface EmployeeRepository
extends CrudRepository<Employee, Long> {
}
Common methods:
save(entity)
findById(id)
findAll()
delete(entity)
deleteById(id)
existsById(id)
count()
JpaRepository
Most commonly used in Spring Boot applications.
public interface EmployeeRepository
extends JpaRepository<Employee, Long> {
}
JpaRepository includes:
CRUD
Pagination
Sorting
Batch delete
Flush methods
JPA-specific operations
Sample Entity
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String department;
private Double salary;
private String status;
public Employee() {
}
public Employee(String name, String department, Double salary, String status) {
this.name = name;
this.department = department;
this.salary = salary;
this.status = status;
}
// getters and setters
}
Basic Repository
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository
extends JpaRepository<Employee, Long> {
}
That single interface gives many methods automatically.
Basic CRUD Service
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Transactional
public Employee createEmployee(Employee employee) {
return employeeRepository.save(employee);
}
@Transactional(readOnly = true)
public Employee getEmployee(Long id) {
return employeeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Employee not found"));
}
@Transactional(readOnly = true)
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
@Transactional
public Employee updateSalary(Long id, Double salary) {
Employee employee = employeeRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Employee not found"));
employee.setSalary(salary);
return employee;
}
@Transactional
public void deleteEmployee(Long id) {
employeeRepository.deleteById(id);
}
}
Important:
updateSalary does not call save.
Because employee is managed.
Dirty checking updates DB.
CRUD Flow Diagram
flowchart TD
A["Client Request"]
B["Controller"]
C["Service"]
D["Repository"]
E["EntityManager"]
F["Hibernate"]
G["Database"]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
save Method
save() is used for both insert and update.
employeeRepository.save(employee);
Internally:
New entity
persist
Existing or detached entity
merge
save Flow
flowchart TD
A["repository.save entity"]
B["Is Entity New"]
C["persist"]
D["merge"]
E["Insert SQL"]
F["Update SQL"]
A --> B
B -->|Yes| C
B -->|No| D
C --> E
D --> F
Example Insert
Employee employee =
new Employee("Venu", "Engineering", 100000.0, "ACTIVE");
employeeRepository.save(employee);
Generated SQL:
INSERT INTO employees
(name, department, salary, status)
VALUES
('Venu', 'Engineering', 100000, 'ACTIVE');
Example Update
@Transactional
public Employee updateDepartment(Long id) {
Employee employee = employeeRepository.findById(id)
.orElseThrow();
employee.setDepartment("Architecture");
return employee;
}
Generated SQL during commit:
UPDATE employees
SET department = 'Architecture'
WHERE id = 1;
Query Methods
Spring Data JPA can generate queries from method names.
List<Employee> findByDepartment(String department);
Generated logic:
SELECT *
FROM employees
WHERE department = ?;
Common Query Methods
List<Employee> findByStatus(String status);
List<Employee> findByDepartmentAndStatus(
String department,
String status
);
List<Employee> findBySalaryGreaterThan(Double salary);
List<Employee> findByNameContaining(String keyword);
List<Employee> findByDepartmentOrderBySalaryDesc(String department);
boolean existsByName(String name);
long countByDepartment(String department);
void deleteByStatus(String status);
Query Method Diagram
flowchart LR
A["Method Name"]
B["Spring Data Parser"]
C["Generate JPQL"]
D["Execute SQL"]
A --> B
B --> C
C --> D
Query Method Keywords
| Keyword | Example |
|---|---|
| And | findByDepartmentAndStatus |
| Or | findByDepartmentOrStatus |
| GreaterThan | findBySalaryGreaterThan |
| LessThan | findBySalaryLessThan |
| Between | findBySalaryBetween |
| Like | findByNameLike |
| Containing | findByNameContaining |
| StartingWith | findByNameStartingWith |
| EndingWith | findByNameEndingWith |
| In | findByDepartmentIn |
| OrderBy | findByDepartmentOrderBySalaryDesc |
JPQL Query using @Query
Use JPQL when method names become too long.
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface EmployeeRepository
extends JpaRepository<Employee, Long> {
@Query("select e from Employee e where e.department = :department")
List<Employee> findEmployeesByDepartment(
@Param("department") String department
);
}
JPQL uses entity names and fields, not table names.
JPQL with Multiple Conditions
@Query("""
select e
from Employee e
where e.department = :department
and e.salary >= :minSalary
and e.status = :status
""")
List<Employee> searchEmployees(
@Param("department") String department,
@Param("minSalary") Double minSalary,
@Param("status") String status
);
Native Query
Use native query when you need database-specific SQL.
@Query(
value = """
SELECT *
FROM employees
WHERE department = :department
""",
nativeQuery = true
)
List<Employee> findByDepartmentNative(
@Param("department") String department
);
JPQL vs Native Query
| Feature | JPQL | Native SQL | |---|---| | Uses | Entity fields | Table columns | | Portable | Yes | Less | | DB-specific features | Limited | Full | | Refactoring safety | Better | Lower | | Recommended | Most cases | Special cases |
Pagination
Use Pageable.
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Page<Employee> findByDepartment(String department, Pageable pageable);
Service:
@Transactional(readOnly = true)
public Page<Employee> getEmployeesByDepartment(
String department,
int page,
int size
) {
Pageable pageable =
PageRequest.of(page, size);
return employeeRepository.findByDepartment(department, pageable);
}
Generated SQL:
SELECT *
FROM employees
WHERE department = ?
LIMIT ? OFFSET ?;
Pagination Flow
flowchart LR
A["Client page and size"]
B["PageRequest"]
C["Repository"]
D["Limit Offset Query"]
E["Page Result"]
A --> B
B --> C
C --> D
D --> E
Sorting
List<Employee> employees =
employeeRepository.findAll(
Sort.by(Sort.Direction.DESC, "salary")
);
Generated SQL:
SELECT *
FROM employees
ORDER BY salary DESC;
Pagination with Sorting
Pageable pageable =
PageRequest.of(
0,
10,
Sort.by(Sort.Direction.DESC, "salary")
);
Page<Employee> page =
employeeRepository.findByDepartment("Engineering", pageable);
Projection
Projection allows fetching only required fields.
Instead of loading full entity:
public interface EmployeeNameSalaryProjection {
String getName();
Double getSalary();
}
Repository:
List<EmployeeNameSalaryProjection>
findByDepartment(String department);
Usage:
List<EmployeeNameSalaryProjection> result =
employeeRepository.findByDepartment("Engineering");
for (EmployeeNameSalaryProjection employee : result) {
System.out.println(employee.getName());
System.out.println(employee.getSalary());
}
Projection Diagram
flowchart TD
A["Query Employee"]
B["Need Only Name Salary"]
C["Projection Interface"]
D["Fetch Selected Columns"]
E["Less Memory"]
A --> B
B --> C
C --> D
D --> E
DTO Projection
public record EmployeeSummaryDto(
String name,
String department,
Double salary
) {
}
Repository:
@Query("""
select new com.codewithvenu.dto.EmployeeSummaryDto(
e.name,
e.department,
e.salary
)
from Employee e
where e.department = :department
""")
List<EmployeeSummaryDto> findEmployeeSummary(
@Param("department") String department
);
Modifying Query
Use @Modifying for update or delete JPQL queries.
import org.springframework.data.jpa.repository.Modifying;
@Modifying
@Query("""
update Employee e
set e.status = :status
where e.department = :department
""")
int updateStatusByDepartment(
@Param("department") String department,
@Param("status") String status
);
Service:
@Transactional
public int deactivateDepartment(String department) {
return employeeRepository.updateStatusByDepartment(
department,
"INACTIVE"
);
}
Important:
Bulk update bypasses persistence context.
Use clearAutomatically if needed.
Better:
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
update Employee e
set e.status = :status
where e.department = :department
""")
int updateStatusByDepartment(
@Param("department") String department,
@Param("status") String status
);
Bulk Update Problem Diagram
flowchart TD
A["Persistence Context Has Employee ACTIVE"]
B["Bulk Update Sets INACTIVE"]
C["Database Updated"]
D["Persistence Context Still ACTIVE"]
E["clearAutomatically"]
F["Fresh Data Next Time"]
A --> B
B --> C
C --> D
D --> E
E --> F
@Lock with Repository
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.Lock;
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select e from Employee e where e.id = :id")
Optional<Employee> findByIdForUpdate(@Param("id") Long id);
Use for high-conflict updates.
@EntityGraph to Avoid N+1 Problem
Suppose Employee has Department.
@Entity
public class Employee {
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
}
Repository:
import org.springframework.data.jpa.repository.EntityGraph;
@EntityGraph(attributePaths = {"department"})
List<Employee> findByStatus(String status);
This fetches employee and department together.
EntityGraph Flow
flowchart TD
A["Repository Method"]
B["@EntityGraph department"]
C["Fetch Employee"]
D["Fetch Department Together"]
E["Avoid N Plus One"]
A --> B
B --> C
C --> D
D --> E
Specifications
Specifications help build dynamic queries.
Repository:
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface EmployeeRepository
extends JpaRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> {
}
Specification:
public class EmployeeSpecification {
public static Specification<Employee> hasDepartment(String department) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("department"), department);
}
public static Specification<Employee> salaryGreaterThan(Double salary) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.greaterThan(root.get("salary"), salary);
}
public static Specification<Employee> hasStatus(String status) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("status"), status);
}
}
Service:
@Transactional(readOnly = true)
public List<Employee> search(
String department,
Double minSalary,
String status
) {
Specification<Employee> spec =
Specification.where(EmployeeSpecification.hasDepartment(department))
.and(EmployeeSpecification.salaryGreaterThan(minSalary))
.and(EmployeeSpecification.hasStatus(status));
return employeeRepository.findAll(spec);
}
Specification Diagram
flowchart LR
A["Search Filters"]
B["Specification 1"]
C["Specification 2"]
D["Specification 3"]
E["Combined Predicate"]
F["Dynamic Query"]
A --> B
A --> C
A --> D
B --> E
C --> E
D --> E
E --> F
Custom Repository
Use custom repository when Spring Data method names are not enough.
Interface:
public interface EmployeeCustomRepository {
List<Employee> searchEmployees(
String department,
Double minSalary,
String status
);
}
Implementation:
@Repository
public class EmployeeCustomRepositoryImpl
implements EmployeeCustomRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Employee> searchEmployees(
String department,
Double minSalary,
String status
) {
String jpql = """
select e
from Employee e
where (:department is null or e.department = :department)
and (:minSalary is null or e.salary >= :minSalary)
and (:status is null or e.status = :status)
""";
return entityManager.createQuery(jpql, Employee.class)
.setParameter("department", department)
.setParameter("minSalary", minSalary)
.setParameter("status", status)
.getResultList();
}
}
Main Repository:
public interface EmployeeRepository
extends JpaRepository<Employee, Long>,
EmployeeCustomRepository {
}
Custom Repository Diagram
flowchart TD
A["EmployeeRepository"]
B["JpaRepository Methods"]
C["Custom Repository Methods"]
D["EntityManager Implementation"]
A --> B
A --> C
C --> D
REST Controller Example
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@PostMapping
public Employee create(@RequestBody Employee employee) {
return employeeService.createEmployee(employee);
}
@GetMapping("/{id}")
public Employee get(@PathVariable Long id) {
return employeeService.getEmployee(id);
}
@GetMapping
public Page<Employee> search(
@RequestParam String department,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return employeeService.getEmployeesByDepartment(
department,
page,
size
);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
employeeService.deleteEmployee(id);
}
}
Repository Best Practices
✅ Use JpaRepository for most applications
✅ Keep business logic in service layer
✅ Use query methods for simple filters
✅ Use @Query for complex fixed queries
✅ Use Specifications for dynamic filters
✅ Use projections for read-only API responses
✅ Use pagination for large result sets
✅ Use @EntityGraph to avoid N+1 queries
✅ Use @Modifying carefully with transactions
Common Mistakes
❌ Writing very long method names
findByDepartmentAndStatusAndSalaryGreaterThanAndNameContaining...
Use @Query or Specification instead.
❌ Returning List for huge data
Use pagination.
❌ Updating entities without transaction
Use @Transactional.
❌ Using entity directly as API response in large apps
Use DTOs.
❌ Forgetting bulk updates bypass persistence context
Use clearAutomatically.
Interview Questions
Q1. What is Spring Data JPA Repository?
It is an abstraction that provides database operations using repository interfaces.
Q2. Difference between CrudRepository and JpaRepository?
CrudRepository provides basic CRUD.
JpaRepository provides CRUD plus pagination, sorting, flushing, and JPA-specific methods.
Q3. Does Spring create repository implementation manually?
No.
Spring generates proxy implementation at runtime.
Q4. What is query method?
A method where Spring generates query based on method name.
Example:
findByDepartmentAndStatus
Q5. Difference between JPQL and native query?
JPQL uses entity names and fields.
Native query uses database tables and columns.
Q6. Why use projections?
To fetch only required fields and improve performance.
Q7. What is @Modifying?
Used for update and delete JPQL queries.
Q8. Why use @EntityGraph?
To fetch related entities eagerly for a specific query and avoid N+1 problem.
Summary
Spring Data JPA Repository reduces boilerplate code and makes database access easier.
Main idea:
Define interface
↓
Extend JpaRepository
↓
Spring creates implementation
↓
Use methods directly
Most used tools:
JpaRepository
Query Methods
@Query
Pagination
Sorting
Projection
@Modifying
@EntityGraph
Specification
Custom Repository
Best rule:
Repository = Database access
Service = Business logic
Controller = API layer