Files
spring-boot-examples/docs/33_jpa repository.md
2025-04-08 19:56:24 +09:00

12 KiB

JPA Repository

스프링 부트에서 JpaRepositorySpring Data JPA 모듈의 핵심 인터페이스로, 데이터베이스와의 상호작용을 추상화하여 CRUD(Create, Read, Update, Delete) 작업과 쿼리 작성을 간소화합니다. JpaRepository는 JPA(Java Persistence API)를 기반으로 동작하며, 엔티티(Entity)와 데이터베이스 테이블 간의 매핑을 통해 객체 지향적으로 데이터를 다룰 수 있게 해줍니다. 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름 규칙(Query Method)이나 @Query 어노테이션을 사용해 데이터베이스 작업을 수행할 수 있습니다.

JpaRepository의 특징

  • 상속 구조: JpaRepositoryCrudRepositoryPagingAndSortingRepository를 확장하며, 기본 CRUD 메서드와 페이징/정렬 기능을 모두 제공합니다.
  • 자동 구현: 인터페이스를 정의하기만 하면 스프링이 런타임에 구현체를 생성합니다.
  • 쿼리 생성: 메서드 이름 규칙을 통해 자동으로 쿼리를 생성하거나, @Query로 커스텀 쿼리 정의 가능.
  • 트랜잭션 관리: 기본적으로 읽기 메서드는 읽기 전용 트랜잭션, 쓰기 메서드는 트랜잭션 내에서 실행됩니다.

JpaRepository 기본 사용법

JpaRepository 인터페이스 생성

JpaRepository를 사용하려면, Repository 인터페이스를 생성하고 JpaRepository를 상속하면 됩니다.

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

JpaRepository<User, Long>User 엔티티를 관리하며, 기본 키(ID)의 타입은 Long
save(), findById(), deleteById() 등의 기본 메서드를 자동으로 제공

JpaRepository의 기본 제공 메서드

JpaRepository를 상속하면 기본적으로 제공되는 메서드는 다음과 같습니다.

메서드 설명
save(T entity) 엔티티를 저장 또는 업데이트
findById(ID id) ID로 엔티티 조회 (Optional 반환)
findAll() 모든 엔티티 조회
findAll(Sort sort) 정렬 조건을 적용하여 모든 엔티티 조회
deleteById(ID id) ID로 엔티티 삭제
delete(T entity) 엔티티 삭제
count() 엔티티 개수 조회
existsById(ID id) 특정 ID의 엔티티 존재 여부 확인
  • save()ID가 있으면 업데이트, 없으면 삽입
  • findById()Optional 반환 → orElseThrow()와 함께 사용 가능

메서드 이름을 이용한 자동 쿼리 생성

Spring Data JPA는 메서드 이름을 분석해 쿼리를 자동 생성합니다. 아래 표는 주요 키워드와 그 설명을 정리한 것입니다. 메서드 이름은 일반적으로 find, exists, count, delete 등의 동사로 시작하며, 조건과 옵션을 조합합니다.

메서드 이름 규칙

키워드 설명
find 조건에 맞는 엔티티를 조회 (단일 또는 리스트 반환) findByName(String name)
findDistinct 중복 제거 후 조회 findDistinctByName(String name)
exists 조건에 맞는 엔티티가 존재하는지 확인 (boolean 반환) existsByName(String name)
count 조건에 맞는 엔티티 수를 반환 countByAgeGreaterThan(int age)
delete 조건에 맞는 엔티티 삭제 deleteByName(String name)
By 조건을 시작하는 구분자 findByName(String name)
And 여러 조건을 AND로 결합 findByNameAndAge(String name, int age)
Or 여러 조건을 OR로 결합 findByNameOrAge(String name, int age)
Is, Equals 특정 필드가 값과 동일한 경우 findByNameIs(String name)
Not 특정 필드가 값과 다른 경우 findByNameNot(String name)
LessThan 특정 필드가 값보다 작은 경우 findByAgeLessThan(int age)
LessThanEqual 특정 필드가 값보다 작거나 같은 경우 findByAgeLessThanEqual(int age)
GreaterThan 특정 필드가 값보다 큰 경우 findByAgeGreaterThan(int age)
GreaterThanEqual 특정 필드가 값보다 크거나 같은 경우 findByAgeGreaterThanEqual(int age)
Between 특정 필드가 두 값 사이에 있는 경우 findByAgeBetween(int start, int end)
Like 특정 필드가 패턴과 일치하는 경우 (SQL의 LIKE) findByNameLike(String pattern)
NotLike 특정 필드가 패턴과 일치하지 않는 경우 findByNameNotLike(String pattern)
StartingWith 특정 필드가 값으로 시작하는 경우 findByNameStartingWith(String prefix)
EndingWith 특정 필드가 값으로 끝나는 경우 findByNameEndingWith(String suffix)
Containing 특정 필드가 값을 포함하는 경우 findByNameContaining(String part)
In 특정 필드가 값 목록에 포함된 경우 findByAgeIn(List<Integer> ages)
NotIn 특정 필드가 값 목록에 포함되지 않은 경우 findByAgeNotIn(List<Integer> ages)
True 특정 boolean 필드가 true인 경우 findByActiveTrue()
False 특정 boolean 필드가 false인 경우 findByActiveFalse()
OrderBy 결과를 특정 필드로 정렬 findByNameOrderByAgeAsc(String name)
Asc 오름차순 정렬 findByNameOrderByAgeAsc(String name)
Desc 내림차순 정렬 findByNameOrderByAgeDesc(String name)
First, Top 결과 중 처음 N개만 반환 (숫자 생략 시 1개) findFirstByName(String name)
FirstN, TopN 결과 중 처음 N개 반환 findTop10ByName(String name)
Page 페이징된 결과 반환 (Page<T> 타입) findByName(String name, Pageable pageable)
Slice 페이징된 결과의 일부 반환 (Slice<T> 타입, 전체 카운트 제외) findByName(String name, Pageable pageable)

단일 필드로 조회

List<User> findByUsername(String username);

SELECT * FROM user WHERE username = ?

여러 조건으로 조회 (AND 조건)

List<User> findByUsernameAndEmail(String username, String email);

SELECT * FROM user WHERE username = ? AND email = ?

여러 조건으로 조회 (OR 조건)

List<User> findByUsernameOrEmail(String username, String email);

SELECT * FROM user WHERE username = ? OR email = ?

특정 값보다 큰 데이터 조회

List<User> findByAgeGreaterThan(int age);

SELECT * FROM user WHERE age > ?

특정 값 포함 여부 조회

List<User> findByUsernameLike(String keyword);

SELECT * FROM user WHERE username LIKE ?

정렬 추가 (OrderBy)

List<User> findByUsernameOrderByAgeDesc(String username);

SELECT * FROM user WHERE username = ? ORDER BY age DESC

페이징 및 정렬 처리

Spring Data JPA는 페이징(Pageable)과 정렬(Sort) 기능을 기본으로 제공합니다.

  • 페이징 주요 용어:

    • Page: 현재 페이지 번호 (0부터 시작).
    • Size: 한 페이지에 포함될 데이터 개수.
    • Total Elements: 전체 데이터 개수.
    • Total Pages: 전체 페이지 수.
  • 장점:

    • 데이터베이스 부하 감소.
    • 사용자 경험 개선 (한 번에 모든 데이터를 로드하지 않음).
  • 정렬 주요 용어:

    • Sort: 정렬 기준과 방향을 정의.
    • Direction: ASC (오름차순), DESC (내림차순).
  • Pageable:

    • 페이징과 정렬 정보를 담는 인터페이스.
    • PageRequest.of(page, size): 기본 페이징 설정.
    • PageRequest.of(page, size, sort): 정렬 포함 페이징 설정.
  • Sort:

    • 정렬 조건을 정의.
    • Sort.by(Direction, "fieldName"): 단일 필드 정렬.
    • Sort.by(Sort.Order.asc("field1"), Sort.Order.desc("field2")): 다중 필드 정렬.
  • Page<T>:

    • 페이징된 결과를 반환하며, 전체 페이지 수, 총 요소 수 등 메타데이터 포함.
    • 주요 메서드: getContent(), getTotalElements(), getTotalPages().
  • Slice<T>:

    • 전체 카운트를 포함하지 않은 페이징 결과 (다음 페이지 존재 여부만 확인 가능).
    • 주로 무한 스크롤에 사용.
Page<User> findByName(String name, Pageable pageable);
Pageable pageable = PageRequest.of(0, 10, Sort.by("age").ascending());
Page<User> users = userRepository.findByName("John", pageable);

페이징을 위한 Pageable 인터페이스 사용

페이징을 적용하려면 Pageable 객체를 매개변수로 전달하면 됩니다.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findByAgeGreaterThan(int age, Pageable pageable);
}

Page<T>를 반환하여 총 페이지 개수, 현재 페이지 정보 등을 확인 가능

페이징 및 정렬을 컨트롤러에서 사용하기

컨트롤러에서 PageRequest.of(페이지번호, 개수, 정렬기준)을 사용하여 페이징을 요청할 수 있습니다.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @GetMapping
    public Page<User> getUsers(@RequestParam(defaultValue = "0") int page,
                               @RequestParam(defaultValue = "10") int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("age").descending());
        return userRepository.findAll(pageable);
    }
}

PageRequest.of(page, size, Sort.by("age").descending())나이 기준 내림차순 정렬
✔ 클라이언트에서 GET /users?page=0&size=10 요청 시 첫 번째 페이지의 10개 데이터 반환

커스텀 쿼리

메서드 이름으로 표현하기 어려운 쿼리는 @Query 사용:

@Query("SELECT u FROM User u WHERE u.name = ?1 AND u.age > ?2")
List<User> findCustomUsers(String name, int age);