Spring Data JPA 쿼리 메서드 완벽 가이드



Spring Data JPA의 쿼리 메서드를 활용하면 SQL 없이 메서드 이름만으로 복잡한 쿼리를 작성할 수 있습니다.



기본 쿼리 메서드


public interface UserRepository extends JpaRepository<User, Long> {
// 기본 조회
Optional<User> findByEmail(String email);
List<User> findByName(String name);

// 복합 조건
List<User> findByNameAndAge(String name, int age);
List<User> findByNameOrEmail(String name, String email);

// 비교 연산
List<User> findByAgeGreaterThan(int age);
List<User> findByAgeLessThanEqual(int age);
List<User> findByAgeBetween(int start, int end);

// NULL 처리
List<User> findByNicknameIsNull();
List<User> findByNicknameIsNotNull();

// LIKE 검색
List<User> findByNameContaining(String keyword);
List<User> findByNameStartingWith(String prefix);
List<User> findByNameEndingWith(String suffix);

// IN 절
List<User> findByAgeIn(Collection<Integer> ages);

// 정렬
List<User> findByNameOrderByAgeDesc(String name);

// 페이징
Page<User> findByAge(int age, Pageable pageable);

// Top/First
List<User> findTop5ByOrderByCreatedAtDesc();
Optional<User> findFirstByNameOrderByAgeDesc(String name);

// 존재 여부
boolean existsByEmail(String email);

// 개수
long countByAge(int age);

// 삭제
void deleteByEmail(String email);
}


@Query 어노테이션


// JPQL
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
List<User> findByEmailDomain(@Param("domain") String domain);

// Native Query
@Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
List<User> findRecentUsers(@Param("date") LocalDateTime date);

// 업데이트
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") String status);

// DTO 프로젝션
@Query("SELECT new com.example.UserDto(u.name, u.email) FROM User u")
List<UserDto> findAllUserDto();


페이징과 정렬


// 서비스 계층
Pageable pageable = PageRequest.of(0, 10, Sort.by("createdAt").descending());
Page<User> page = userRepository.findByAge(25, pageable);

// 결과 활용
List<User> users = page.getContent();
long total = page.getTotalElements();
int totalPages = page.getTotalPages();
boolean hasNext = page.hasNext();


동적 쿼리 (Specification)


public class UserSpecification {
public static Specification<User> hasName(String name) {
return (root, query, cb) ->
name == null ? null : cb.equal(root.get("name"), name);
}

public static Specification<User> ageGreaterThan(Integer age) {
return (root, query, cb) ->
age == null ? null : cb.greaterThan(root.get("age"), age);
}
}

// 사용
List<User> users = userRepository.findAll(
Specification.where(hasName("Kim")).and(ageGreaterThan(20))
);


프로젝션 (필요한 필드만)


// 인터페이스 프로젝션
public interface UserSummary {
String getName();
String getEmail();
}

List<UserSummary> findByAge(int age);

// 클래스 프로젝션 (DTO)
public record UserDto(String name, String email) {}

@Query("SELECT new com.example.UserDto(u.name, u.email) FROM User u")
List<UserDto> findAllDto();


N+1 문제 해결


// Fetch Join
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

// EntityGraph
@EntityGraph(attributePaths = {"orders", "addresses"})
Optional<User> findById(Long id);