2025-04-08T19:56:24

This commit is contained in:
2025-04-08 19:56:24 +09:00
parent a75a1dbd0f
commit eef061c1c9
100 changed files with 18639 additions and 0 deletions

224
docs/33_jpa repository.md Normal file
View File

@@ -0,0 +1,224 @@
# 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<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<T> 반환 → `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)` |
#### 단일 필드로 조회
```java
List<User> findByUsername(String username);
```
`SELECT * FROM user WHERE username = ?`
#### 여러 조건으로 조회 (AND 조건)
```java
List<User> findByUsernameAndEmail(String username, String email);
```
`SELECT * FROM user WHERE username = ? AND email = ?`
#### 여러 조건으로 조회 (OR 조건)
```java
List<User> findByUsernameOrEmail(String username, String email);
```
`SELECT * FROM user WHERE username = ? OR email = ?`
#### 특정 값보다 큰 데이터 조회
```java
List<User> findByAgeGreaterThan(int age);
```
`SELECT * FROM user WHERE age > ?`
#### 특정 값 포함 여부 조회
```java
List<User> findByUsernameLike(String keyword);
```
`SELECT * FROM user WHERE username LIKE ?`
#### 정렬 추가 (`OrderBy`)
```java
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>`**:
- 전체 카운트를 포함하지 않은 페이징 결과 (다음 페이지 존재 여부만 확인 가능).
- 주로 무한 스크롤에 사용.
```java
Page<User> findByName(String name, Pageable pageable);
```
```java
Pageable pageable = PageRequest.of(0, 10, Sort.by("age").ascending());
Page<User> 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<User, Long> {
Page<User> findByAgeGreaterThan(int age, Pageable pageable);
}
```
`Page<T>`를 반환하여 총 페이지 개수, 현재 페이지 정보 등을 확인 가능
### 페이징 및 정렬을 컨트롤러에서 사용하기
컨트롤러에서 `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<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` 사용:
```java
@Query("SELECT u FROM User u WHERE u.name = ?1 AND u.age > ?2")
List<User> findCustomUsers(String name, int age);
```