12 KiB
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를 상속하면 됩니다.
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);