Spring Data JPA Fundamentals in Java

Introduction to Spring Data JPA

Spring Data JPA is part of the larger Spring Data family that makes it easy to implement JPA-based repositories. It reduces the amount of boilerplate code required to implement data access layers for both relational and non-relational databases.

Key Benefits

  • Reduced Boilerplate: Automatic repository implementation
  • Built-in CRUD Operations: Common operations out of the box
  • Dynamic Query Generation: From method names
  • Pagination and Sorting: Built-in support
  • Integration with Spring Ecosystem: Seamless Spring integration

Project Setup

Maven Dependencies

<dependencies>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Database Driver (H2 for example) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Alternatively, for PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Starter Web (for REST controllers) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>

Application Configuration

# application.properties
# H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA Configuration
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# H2 Console (for development)
spring.h2.console.enabled=true
# For PostgreSQL
# spring.datasource.url=jdbc:postgresql://localhost:5432/mydatabase
# spring.datasource.username=postgres
# spring.datasource.password=password
# spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
# spring.jpa.hibernate.ddl-auto=update

Entity Modeling

Basic Entity

package com.example.entity;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
@Column(name = "email", unique = true, nullable = false)
private String email;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
@Column(name = "age")
private Integer age;
@Enumerated(EnumType.STRING)
@Column(name = "status")
private UserStatus status = UserStatus.ACTIVE;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Default constructor (required by JPA)
public User() {}
// Constructor for required fields
public User(String username, String email, String firstName, String lastName) {
this.username = username;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// Pre-persist and pre-update callbacks
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public UserStatus getStatus() { return status; }
public void setStatus(UserStatus status) { this.status = status; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
// equals and hashCode
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id) && 
Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(id, username);
}
// toString
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
", status=" + status +
'}';
}
}
enum UserStatus {
ACTIVE, INACTIVE, SUSPENDED
}

Entity Relationships

package com.example.entity;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", unique = true, nullable = false)
private String name;
@Column(name = "description")
private String description;
// One-to-Many relationship with Employee
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Employee> employees = new ArrayList<>();
// Constructors
public Department() {}
public Department(String name, String description) {
this.name = name;
this.description = description;
}
// Helper methods for bidirectional relationship
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
public void removeEmployee(Employee employee) {
employees.remove(employee);
employee.setDepartment(null);
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<Employee> getEmployees() { return employees; }
public void setEmployees(List<Employee> employees) { this.employees = employees; }
}
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "employee_id", unique = true, nullable = false)
private String employeeId;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name", nullable = false)
private String lastName;
@Column(name = "email")
private String email;
@Column(name = "salary")
private Double salary;
// Many-to-One relationship with Department
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
// Many-to-Many relationship with Project
@ManyToMany
@JoinTable(
name = "employee_project",
joinColumns = @JoinColumn(name = "employee_id"),
inverseJoinColumns = @JoinColumn(name = "project_id")
)
private List<Project> projects = new ArrayList<>();
// Constructors
public Employee() {}
public Employee(String employeeId, String firstName, String lastName, String email) {
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getEmployeeId() { return employeeId; }
public void setEmployeeId(String employeeId) { this.employeeId = employeeId; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Double getSalary() { return salary; }
public void setSalary(Double salary) { this.salary = salary; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
public List<Project> getProjects() { return projects; }
public void setProjects(List<Project> projects) { this.projects = projects; }
}
@Entity
@Table(name = "projects")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "description")
private String description;
@Column(name = "budget")
private Double budget;
// Many-to-Many relationship with Employee (inverse side)
@ManyToMany(mappedBy = "projects")
private List<Employee> employees = new ArrayList<>();
// Constructors, Getters and Setters
public Project() {}
public Project(String name, String description, Double budget) {
this.name = name;
this.description = description;
this.budget = budget;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Double getBudget() { return budget; }
public void setBudget(Double budget) { this.budget = budget; }
public List<Employee> getEmployees() { return employees; }
public void setEmployees(List<Employee> employees) { this.employees = employees; }
}

Repository Interfaces

Basic Repository Interface

package com.example.repository;
import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Basic CRUD operations are inherited
}

Custom Repository with Query Methods

package com.example.repository;
import com.example.entity.User;
import com.example.entity.UserStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Derived Query Methods (automatically implemented by Spring Data JPA)
// Find by exact match
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
// Find by multiple conditions
List<User> findByFirstNameAndLastName(String firstName, String lastName);
List<User> findByFirstNameOrLastName(String firstName, String lastName);
// Find with comparison operators
List<User> findByAgeGreaterThan(int age);
List<User> findByAgeLessThanEqual(int age);
List<User> findByAgeBetween(int startAge, int endAge);
// Find with null checks
List<User> findByLastNameIsNull();
List<User> findByLastNameIsNotNull();
// Find with like/contains
List<User> findByFirstNameContaining(String firstName);
List<User> findByFirstNameLike(String pattern);
List<User> findByFirstNameStartingWith(String prefix);
List<User> findByFirstNameEndingWith(String suffix);
// Find with ordering
List<User> findByStatusOrderByFirstNameAsc(UserStatus status);
List<User> findByStatusOrderByCreatedAtDesc(UserStatus status);
// Find with limit
List<User> findTop5ByOrderByCreatedAtDesc();
List<User> findFirst10ByStatus(UserStatus status);
// Count operations
long countByStatus(UserStatus status);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
// Delete operations
void deleteByStatus(UserStatus status);
long deleteByLastName(String lastName);
// Custom JPQL Queries
@Query("SELECT u FROM User u WHERE u.email LIKE '%@gmail.com'")
List<User> findGmailUsers();
@Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.age > :age")
List<User> findUsersByFirstNameAndMinAge(@Param("firstName") String firstName, 
@Param("age") int minAge);
@Query("SELECT u.firstName, u.lastName FROM User u WHERE u.status = :status")
List<Object[]> findUserNamesByStatus(@Param("status") UserStatus status);
// Native SQL Query
@Query(value = "SELECT * FROM users u WHERE u.created_at > :date", nativeQuery = true)
List<User> findUsersCreatedAfter(@Param("date") LocalDateTime date);
// Update query
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("status") UserStatus status, 
@Param("date") LocalDateTime date);
// Complex query with aggregation
@Query("SELECT u.status, COUNT(u) FROM User u GROUP BY u.status")
List<Object[]> countUsersByStatus();
}

Employee Repository with Relationships

package com.example.repository;
import com.example.entity.Department;
import com.example.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// Basic queries
Optional<Employee> findByEmployeeId(String employeeId);
List<Employee> findByFirstNameContainingIgnoreCase(String firstName);
List<Employee> findByLastNameContainingIgnoreCase(String lastName);
// Relationship queries
List<Employee> findByDepartment(Department department);
List<Employee> findByDepartmentName(String departmentName);
// Salary-based queries
List<Employee> findBySalaryGreaterThan(Double salary);
List<Employee> findBySalaryBetween(Double minSalary, Double maxSalary);
// Complex queries with joins
@Query("SELECT e FROM Employee e JOIN e.department d WHERE d.name = :deptName AND e.salary > :minSalary")
List<Employee> findEmployeesInDepartmentWithMinSalary(@Param("deptName") String departmentName, 
@Param("minSalary") Double minSalary);
// Project count for employees
@Query("SELECT e, SIZE(e.projects) FROM Employee e")
List<Object[]> findEmployeesWithProjectCount();
// Employees working on specific project
@Query("SELECT e FROM Employee e JOIN e.projects p WHERE p.name = :projectName")
List<Employee> findEmployeesByProjectName(@Param("projectName") String projectName);
// Average salary by department
@Query("SELECT d.name, AVG(e.salary) FROM Employee e JOIN e.department d GROUP BY d.name")
List<Object[]> findAverageSalaryByDepartment();
}

Service Layer

User Service

package com.example.service;
import com.example.entity.User;
import com.example.entity.UserStatus;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
// Basic CRUD operations
public User createUser(User user) {
return userRepository.save(user);
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User updateUser(User user) {
if (!userRepository.existsById(user.getId())) {
throw new RuntimeException("User not found with id: " + user.getId());
}
return userRepository.save(user);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// Custom business logic methods
public Optional<User> getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
public Optional<User> getUserByEmail(String email) {
return userRepository.findByEmail(email);
}
public List<User> getUsersByStatus(UserStatus status) {
return userRepository.findByStatus(status);
}
public List<User> searchUsersByName(String name) {
return userRepository.findByFirstNameContainingOrLastNameContaining(name, name);
}
public List<User> getUsersByAgeRange(int minAge, int maxAge) {
return userRepository.findByAgeBetween(minAge, maxAge);
}
public long countActiveUsers() {
return userRepository.countByStatus(UserStatus.ACTIVE);
}
public boolean usernameExists(String username) {
return userRepository.existsByUsername(username);
}
public boolean emailExists(String email) {
return userRepository.existsByEmail(email);
}
// Pagination and sorting
public Page<User> getUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
public Page<User> getActiveUsers(Pageable pageable) {
return userRepository.findByStatus(UserStatus.ACTIVE, pageable);
}
// Bulk operations
@Transactional
public void deactivateInactiveUsers(LocalDateTime cutoffDate) {
userRepository.deactivateInactiveUsers(UserStatus.INACTIVE, cutoffDate);
}
// Complex business logic
@Transactional
public User registerUser(User user) {
if (usernameExists(user.getUsername())) {
throw new RuntimeException("Username already exists: " + user.getUsername());
}
if (emailExists(user.getEmail())) {
throw new RuntimeException("Email already exists: " + user.getEmail());
}
user.setStatus(UserStatus.ACTIVE);
return userRepository.save(user);
}
@Transactional
public void deactivateUser(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found with id: " + userId));
user.setStatus(UserStatus.INACTIVE);
userRepository.save(user);
}
}

Department Service with Relationships

package com.example.service;
import com.example.entity.Department;
import com.example.entity.Employee;
import com.example.repository.DepartmentRepository;
import com.example.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
@Transactional
public class DepartmentService {
@Autowired
private DepartmentRepository departmentRepository;
@Autowired
private EmployeeRepository employeeRepository;
public Department createDepartment(Department department) {
return departmentRepository.save(department);
}
public Optional<Department> getDepartmentById(Long id) {
return departmentRepository.findById(id);
}
public List<Department> getAllDepartments() {
return departmentRepository.findAll();
}
public Department updateDepartment(Department department) {
if (!departmentRepository.existsById(department.getId())) {
throw new RuntimeException("Department not found with id: " + department.getId());
}
return departmentRepository.save(department);
}
public void deleteDepartment(Long id) {
departmentRepository.deleteById(id);
}
// Relationship management
@Transactional
public void addEmployeeToDepartment(Long departmentId, Employee employee) {
Department department = departmentRepository.findById(departmentId)
.orElseThrow(() -> new RuntimeException("Department not found"));
department.addEmployee(employee);
departmentRepository.save(department);
}
@Transactional
public void removeEmployeeFromDepartment(Long departmentId, Long employeeId) {
Department department = departmentRepository.findById(departmentId)
.orElseThrow(() -> new RuntimeException("Department not found"));
Employee employee = employeeRepository.findById(employeeId)
.orElseThrow(() -> new RuntimeException("Employee not found"));
department.removeEmployee(employee);
departmentRepository.save(department);
}
public List<Employee> getEmployeesInDepartment(Long departmentId) {
Department department = departmentRepository.findById(departmentId)
.orElseThrow(() -> new RuntimeException("Department not found"));
return department.getEmployees();
}
// Business logic
public Optional<Department> getDepartmentByName(String name) {
return departmentRepository.findByName(name);
}
public List<Object[]> getDepartmentStatistics() {
return departmentRepository.getDepartmentStatistics();
}
}

REST Controllers

User Controller

package com.example.controller;
import com.example.entity.User;
import com.example.entity.UserStatus;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
@GetMapping("/paged")
public ResponseEntity<Page<User>> getUsersPaged(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String sortDirection) {
Sort sort = sortDirection.equalsIgnoreCase("desc") 
? Sort.by(sortBy).descending() 
: Sort.by(sortBy).ascending();
Pageable pageable = PageRequest.of(page, size, sort);
Page<User> users = userService.getUsers(pageable);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> user = userService.getUserById(id);
return user.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/username/{username}")
public ResponseEntity<User> getUserByUsername(@PathVariable String username) {
Optional<User> user = userService.getUserByUsername(username);
return user.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/status/{status}")
public ResponseEntity<List<User>> getUsersByStatus(@PathVariable UserStatus status) {
List<User> users = userService.getUsersByStatus(status);
return ResponseEntity.ok(users);
}
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
try {
User createdUser = userService.registerUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
} catch (RuntimeException e) {
return ResponseEntity.badRequest().build();
}
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User user) {
user.setId(id);
try {
User updatedUser = userService.updateUser(user);
return ResponseEntity.ok(updatedUser);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.getUserById(id).isPresent()) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
@PatchMapping("/{id}/deactivate")
public ResponseEntity<Void> deactivateUser(@PathVariable Long id) {
try {
userService.deactivateUser(id);
return ResponseEntity.ok().build();
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/search")
public ResponseEntity<List<User>> searchUsers(@RequestParam String name) {
List<User> users = userService.searchUsersByName(name);
return ResponseEntity.ok(users);
}
@GetMapping("/count/active")
public ResponseEntity<Long> countActiveUsers() {
long count = userService.countActiveUsers();
return ResponseEntity.ok(count);
}
}

Advanced Features

Custom Repository Implementation

// Custom repository interface
package com.example.repository;
import com.example.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface CustomUserRepository {
List<User> findUsersWithComplexCriteria(String name, Integer minAge, Integer maxAge);
Page<User> findUsersWithPagination(String searchTerm, Pageable pageable);
}
// Implementation
package com.example.repository.impl;
import com.example.entity.User;
import com.example.repository.CustomUserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<User> findUsersWithComplexCriteria(String name, Integer minAge, Integer maxAge) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (name != null && !name.isEmpty()) {
Predicate firstNamePredicate = cb.like(user.get("firstName"), "%" + name + "%");
Predicate lastNamePredicate = cb.like(user.get("lastName"), "%" + name + "%");
predicates.add(cb.or(firstNamePredicate, lastNamePredicate));
}
if (minAge != null) {
predicates.add(cb.greaterThanOrEqualTo(user.get("age"), minAge));
}
if (maxAge != null) {
predicates.add(cb.lessThanOrEqualTo(user.get("age"), maxAge));
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query).getResultList();
}
@Override
public Page<User> findUsersWithPagination(String searchTerm, Pageable pageable) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
if (searchTerm != null && !searchTerm.isEmpty()) {
Predicate searchPredicate = cb.or(
cb.like(cb.lower(user.get("firstName")), "%" + searchTerm.toLowerCase() + "%"),
cb.like(cb.lower(user.get("lastName")), "%" + searchTerm.toLowerCase() + "%"),
cb.like(cb.lower(user.get("email")), "%" + searchTerm.toLowerCase() + "%")
);
query.where(searchPredicate);
}
TypedQuery<User> typedQuery = entityManager.createQuery(query);
typedQuery.setFirstResult((int) pageable.getOffset());
typedQuery.setMaxResults(pageable.getPageSize());
List<User> result = typedQuery.getResultList();
// Get total count for pagination
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
Root<User> countRoot = countQuery.from(User.class);
countQuery.select(cb.count(countRoot));
if (searchTerm != null && !searchTerm.isEmpty()) {
Predicate searchPredicate = cb.or(
cb.like(cb.lower(countRoot.get("firstName")), "%" + searchTerm.toLowerCase() + "%"),
cb.like(cb.lower(countRoot.get("lastName")), "%" + searchTerm.toLowerCase() + "%"),
cb.like(cb.lower(countRoot.get("email")), "%" + searchTerm.toLowerCase() + "%")
);
countQuery.where(searchPredicate);
}
Long total = entityManager.createQuery(countQuery).getSingleResult();
return new PageImpl<>(result, pageable, total);
}
}
// Extend the main repository interface
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
// ... other methods
}

Auditing with Spring Data JPA

// Configuration
@Configuration
@EnableJpaAuditing
public class AuditingConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return new SpringSecurityAuditorAware();
}
}
// AuditorAware implementation
@Component
public class SpringSecurityAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// Get current username from Spring Security
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
}
return Optional.of(authentication.getName());
}
}
// Auditable entity
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class Auditable {
@CreatedDate
@Column(name = "created_date")
private LocalDateTime createdDate;
@LastModifiedDate
@Column(name = "last_modified_date")
private LocalDateTime lastModifiedDate;
@CreatedBy
@Column(name = "created_by")
private String createdBy;
@LastModifiedBy
@Column(name = "last_modified_by")
private String lastModifiedBy;
// Getters and Setters
}

Testing

package com.example.repository;
import com.example.entity.User;
import com.example.entity.UserStatus;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void testSaveUser() {
User user = new User("testuser", "[email protected]", "John", "Doe");
user.setAge(30);
User savedUser = userRepository.save(user);
assertNotNull(savedUser.getId());
assertEquals("testuser", savedUser.getUsername());
}
@Test
public void testFindByUsername() {
User user = new User("testuser", "[email protected]", "John", "Doe");
userRepository.save(user);
Optional<User> foundUser = userRepository.findByUsername("testuser");
assertTrue(foundUser.isPresent());
assertEquals("John", foundUser.get().getFirstName());
}
@Test
public void testFindByAgeGreaterThan() {
User user1 = new User("user1", "[email protected]", "Alice", "Smith");
user1.setAge(25);
User user2 = new User("user2", "[email protected]", "Bob", "Johnson");
user2.setAge(35);
userRepository.saveAll(List.of(user1, user2));
List<User> users = userRepository.findByAgeGreaterThan(30);
assertEquals(1, users.size());
assertEquals("Bob", users.get(0).getFirstName());
}
@Test
public void testPagination() {
// Create test data
for (int i = 1; i <= 15; i++) {
User user = new User("user" + i, "user" + i + "@example.com", "User" + i, "Test");
userRepository.save(user);
}
PageRequest pageable = PageRequest.of(0, 10, Sort.by("username").ascending());
Page<User> page = userRepository.findAll(pageable);
assertEquals(15, page.getTotalElements());
assertEquals(2, page.getTotalPages());
assertEquals(10, page.getContent().size());
}
@Test
public void testCustomQuery() {
User user = new User("testuser", "[email protected]", "John", "Doe");
userRepository.save(user);
List<User> gmailUsers = userRepository.findGmailUsers();
assertFalse(gmailUsers.isEmpty());
assertTrue(gmailUsers.get(0).getEmail().endsWith("@gmail.com"));
}
}

Best Practices

  1. Use Lazy Loading for relationships to avoid N+1 query problems
  2. Implement equals() and hashCode() correctly for entities
  3. Use @Transactional appropriately in service layer
  4. Avoid Entity in DTO - use separate DTO classes for API
  5. Use Pagination for large datasets
  6. Implement Proper Error Handling
  7. Use Projections for read-only operations
  8. Monitor SQL Queries in development
  9. Use Connection Pooling in production
  10. Implement Caching where appropriate

This comprehensive guide covers the fundamentals of Spring Data JPA, from basic entity mapping to advanced features like custom repositories, auditing, and testing.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper