# JPA Repository 스프링 부트에서 `JpaRepository`는 `Spring Data JPA` 모듈의 핵심 인터페이스로, 데이터베이스와의 상호작용을 추상화하여 CRUD(Create, Read, Update, Delete) 작업과 쿼리 작성을 간소화합니다. `JpaRepository`는 JPA(Java Persistence API)를 기반으로 동작하며, 엔티티(Entity)와 데이터베이스 테이블 간의 매핑을 통해 객체 지향적으로 데이터를 다룰 수 있게 해줍니다. 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름 규칙(Query Method)이나 `@Query` 어노테이션을 사용해 데이터베이스 작업을 수행할 수 있습니다. ## JpaRepository의 특징 - **상속 구조**: `JpaRepository`는 `CrudRepository`와 `PagingAndSortingRepository`를 확장하며, 기본 CRUD 메서드와 페이징/정렬 기능을 모두 제공합니다. - **자동 구현**: 인터페이스를 정의하기만 하면 스프링이 런타임에 구현체를 생성합니다. - **쿼리 생성**: 메서드 이름 규칙을 통해 자동으로 쿼리를 생성하거나, `@Query`로 커스텀 쿼리 정의 가능. - **트랜잭션 관리**: 기본적으로 읽기 메서드는 읽기 전용 트랜잭션, 쓰기 메서드는 트랜잭션 내에서 실행됩니다. ## JpaRepository 기본 사용법 ### JpaRepository 인터페이스 생성 JpaRepository를 사용하려면, **Repository 인터페이스를 생성하고 JpaRepository를 상속**하면 됩니다. ```java import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { } ``` ✔ `JpaRepository` → **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 ages)` | | `NotIn` | 특정 필드가 값 목록에 포함되지 않은 경우 `findByAgeNotIn(List 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` 타입) `findByName(String name, Pageable pageable)` | | `Slice` | 페이징된 결과의 일부 반환 (`Slice` 타입, 전체 카운트 제외) `findByName(String name, Pageable pageable)` | #### 단일 필드로 조회 ```java List findByUsername(String username); ``` ✔ `SELECT * FROM user WHERE username = ?` #### 여러 조건으로 조회 (AND 조건) ```java List findByUsernameAndEmail(String username, String email); ``` ✔ `SELECT * FROM user WHERE username = ? AND email = ?` #### 여러 조건으로 조회 (OR 조건) ```java List findByUsernameOrEmail(String username, String email); ``` ✔ `SELECT * FROM user WHERE username = ? OR email = ?` #### 특정 값보다 큰 데이터 조회 ```java List findByAgeGreaterThan(int age); ``` ✔ `SELECT * FROM user WHERE age > ?` #### 특정 값 포함 여부 조회 ```java List findByUsernameLike(String keyword); ``` ✔ `SELECT * FROM user WHERE username LIKE ?` #### 정렬 추가 (`OrderBy`) ```java List 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`**: - 페이징된 결과를 반환하며, 전체 페이지 수, 총 요소 수 등 메타데이터 포함. - 주요 메서드: `getContent()`, `getTotalElements()`, `getTotalPages()`. - **`Slice`**: - 전체 카운트를 포함하지 않은 페이징 결과 (다음 페이지 존재 여부만 확인 가능). - 주로 무한 스크롤에 사용. ```java Page findByName(String name, Pageable pageable); ``` ```java Pageable pageable = PageRequest.of(0, 10, Sort.by("age").ascending()); Page users = userRepository.findByName("John", pageable); ``` ### 페이징을 위한 `Pageable` 인터페이스 사용 페이징을 적용하려면 `Pageable` 객체를 매개변수로 전달하면 됩니다. ```java import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { Page findByAgeGreaterThan(int age, Pageable pageable); } ``` ✔ `Page`를 반환하여 총 페이지 개수, 현재 페이지 정보 등을 확인 가능 ### 페이징 및 정렬을 컨트롤러에서 사용하기 컨트롤러에서 `PageRequest.of(페이지번호, 개수, 정렬기준)`을 사용하여 페이징을 요청할 수 있습니다. ```java 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 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` 사용: ```java @Query("SELECT u FROM User u WHERE u.name = ?1 AND u.age > ?2") List findCustomUsers(String name, int age); ```