Java Notes
Complete guide to Spring Data JPA - entity mapping, repository interfaces, query methods, JPQL, native queries, relationships, and database operations.
What is Spring Data JPA?
Spring Data JPA is a layer on top of JPA (Java Persistence API) that eliminates boilerplate database code. You define repository interfaces, and Spring automatically generates the implementation.
Entity Mapping
@Entity
@Table(name = "students")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(unique = true, nullable = false)
private String email;
@Column(name = "phone_number")
private String phone;
@Enumerated(EnumType.STRING)
private Gender gender;
@Column(name = "date_of_birth")
private LocalDate dateOfBirth;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
public enum Gender {
MALE, FEMALE, OTHER
}
}Repository Interface
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
// Derived query methods (Spring generates SQL automatically)
Optional<Student> findByEmail(String email);
List<Student> findByName(String name);
List<Student> findByGender(Student.Gender gender);
List<Student> findByNameContainingIgnoreCase(String keyword);
List<Student> findByAgeGreaterThan(int age);
List<Student> findByCreatedAtAfter(LocalDateTime date);
List<Student> findByNameAndEmail(String name, String email);
List<Student> findByGenderOrderByNameAsc(Student.Gender gender);
boolean existsByEmail(String email);
long countByGender(Student.Gender gender);
void deleteByEmail(String email);
// Pagination and Sorting
Page<Student> findByGender(Student.Gender gender, Pageable pageable);
// JPQL Queries
@Query("SELECT s FROM Student s WHERE s.email LIKE %:domain")
List<Student> findByEmailDomain(@Param("domain") String domain);
@Query("SELECT s FROM Student s WHERE YEAR(s.dateOfBirth) = :year")
List<Student> findByBirthYear(@Param("year") int year);
@Query("SELECT s.gender, COUNT(s) FROM Student s GROUP BY s.gender")
List<Object[]> countByGenderGrouped();
// Native SQL Queries
@Query(value = "SELECT * FROM students WHERE TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE()) > :age",
nativeQuery = true)
List<Student> findOlderThan(@Param("age") int age);
// Update query
@Modifying
@Transactional
@Query("UPDATE Student s SET s.email = :email WHERE s.id = :id")
int updateEmail(@Param("id") Long id, @Param("email") String email);
}JPA Relationships
One-to-Many / Many-to-One
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Employee> employees = new ArrayList<>();
}
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
}Many-to-Many
@Entity
public class Student {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
public class Course {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}One-to-One
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id")
private UserProfile profile;
}Service Layer with JPA
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StudentService {
private final StudentRepository studentRepository;
public List<Student> getAllStudents() {
return studentRepository.findAll();
}
public Page<Student> getStudentsPaginated(int page, int size, String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return studentRepository.findAll(pageable);
}
public Student getStudentById(Long id) {
return studentRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Student not found: " + id));
}
@Transactional
public Student createStudent(Student student) {
if (studentRepository.existsByEmail(student.getEmail())) {
throw new DuplicateResourceException("Email already exists");
}
return studentRepository.save(student);
}
@Transactional
public Student updateStudent(Long id, Student details) {
Student student = getStudentById(id);
student.setName(details.getName());
student.setEmail(details.getEmail());
student.setPhone(details.getPhone());
return studentRepository.save(student);
}
@Transactional
public void deleteStudent(Long id) {
Student student = getStudentById(id);
studentRepository.delete(student);
}
}Derived Query Method Keywords
| Keyword | Example | SQL Equivalent |
|---|---|---|
| findBy | findByName(name) | WHERE name = ? |
| findByNot | findByNameNot(name) | WHERE name != ? |
| And | findByNameAndAge(n, a) | WHERE name=? AND age=? |
| Or | findByNameOrEmail(n, e) | WHERE name=? OR email=? |
| Between | findByAgeBetween(a, b) | WHERE age BETWEEN ? AND ? |
| LessThan | findByAgeLessThan(age) | WHERE age < ? |
| GreaterThan | findByAgeGreaterThan(age) | WHERE age > ? |
| Like | findByNameLike(pattern) | WHERE name LIKE ? |
| Containing | findByNameContaining(str) | WHERE name LIKE %?% |
| StartingWith | findByNameStartingWith(s) | WHERE name LIKE ?% |
| OrderBy | findByGenderOrderByName() | ORDER BY name |
| IsNull | findByPhoneIsNull() | WHERE phone IS NULL |
| In | findByAgeIn(List ages) | WHERE age IN (?,?,?) |
| Top/First | findTop5ByOrderByAge() | LIMIT 5 |
Interview Key Points
- Spring Data JPA generates repository implementations automatically
JpaRepositoryextendsPagingAndSortingRepositoryextendsCrudRepository- Method names are parsed into queries (findByNameAndEmail → WHERE name=? AND email=?)
@Queryfor complex JPQL/native queries@Modifying+@Transactionalrequired for UPDATE/DELETE queriesFetchType.LAZYloads related entities on access; EAGER loads immediatelycascade = CascadeType.ALLpropagates operations to related entities@PrePersist/@PreUpdatefor automatic timestamps
Summary
In this chapter, we learned about Spring Data JPA in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Spring Data JPA.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, spring, framework, boot
Related Java Master Course Topics