-
Notifications
You must be signed in to change notification settings - Fork 0
[Suhhee] Week7 미션 #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| # Page와 Slice | ||
|
|
||
| > **목적 / 용도** | ||
| > - **Page:** 게시판처럼 정확한 페이지 번호가 필요한 경우 | ||
| > - **Slice:** 무한 스크롤이나 더 보기 버튼이 필요한 경우 | ||
|
|
||
| | 구분 | Page | Slice | | ||
| | --- | --- | --- | | ||
| | **특징** | 전체 데이터 건수, 전체 페이지 수를 알 수 있음 | 전체 건수는 모르고 다음 페이지가 있는지의 여부만 알 수 있음 | | ||
| | **동작 방식** | 데이터 조회 쿼리 + 전체 개수를 세는 `COUNT` 쿼리가 같이 실행됨 | • `COUNT` 쿼리 실행 안 함<br>• 요청한 개수보다 **1개 더 (Limit + 1)** 가져와서 다음 페이지 여부 확인 | | ||
| | **성능** | 데이터가 많아질수록 `COUNT` 쿼리 때문에 성능이 저하될 수 있음 | `COUNT` 쿼리가 없어서 대용량 데이터 조회 시 성능이 훨씬 빠름 | | ||
| | **상속 구조** | `Slice` 인터페이스를 상속받음 (Slice의 모든 기능 + COUNT 기능) | 부모 인터페이스 | | ||
| | **UI** | [1] [2] [3] [4] [5][다음] | 스크롤을 맨 아래로 내리면 자동 로딩 | | ||
|
|
||
| **e.g.** | ||
| ```java | ||
| @Repository | ||
| public interface ReviewRepository extends JpaRepository<Review, Long> { | ||
|
|
||
| // Page 타입 반환 | ||
| Page<Review> findPageBy(Pageable pageable); | ||
|
|
||
| // Slice 타입 반환 | ||
| Slice<Review> findSliceBy(Pageable pageable); | ||
| } | ||
|
|
||
| @RestController | ||
| // ... | ||
| /** | ||
| Page 방식 | ||
| /reviews/page?page=0&size=5 | ||
| **/ | ||
| @GetMapping("/reviews/page") | ||
| public Page<Review> getReviewsByPage( | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "5") int size | ||
| ) { | ||
| // id 기준 최신순 정렬 Pageable 객체 생성 | ||
| Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id")); | ||
|
|
||
| // 반환 시 전체 개수 정보가 응답됨 | ||
| return reviewRepository.findPageBy(pageable); | ||
| } | ||
|
|
||
| /** | ||
| Slice 방식 | ||
| /reviews/slice?page=0&size=5 | ||
| **/ | ||
| @GetMapping("/reviews/slice") | ||
| public Slice<Review> getReviewsBySlice( | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "5") int size | ||
| ) { | ||
| Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id")); | ||
|
|
||
| // 전체 개수는 안 나옴 | ||
| // 데이터 내용, 다음 페이지 여부만 응답됨 | ||
| return reviewRepository.findSliceBy(pageable); | ||
| } | ||
| ``` | ||
|
|
||
| # Java Stream API | ||
|
|
||
| > **Stream API** | ||
| > 자바 8에 추가된 기능으로 컬렉션(List, Set, Map)이나 배열에 저장된 데이터를 반복문 없이 깔끔하게 처리하는 기술. | ||
| > 데이터의 흐름을 만듦 → 람다식과 함께 데이터를 필터링, 변환, 수집 작업을 연결해서 수행. | ||
|
|
||
| ### Stream API 동작 흐름 | ||
| 1. **생성(Creation)** → 컬렉션이나 배열을 스트림 객체로 만듦 (`.stream()`) | ||
| 2. **중간 연산(Intermediate)** → 데이터를 가공함 (여러 번 연결 가능) | ||
| 3. **최종 연산(Terminal)** → 가공된 데이터를 결과로 만듦 (한 번만 사용 가능하며, 이때 실제 연산이 시작됨) | ||
|
|
||
| --- | ||
|
|
||
| ### 중간 연산 | ||
|
|
||
| * **특징:** | ||
| * 데이터를 가공하는 단계 | ||
| * 연산 결과로 또 다른 `Stream`을 반환함 → 메서드 체이닝 가능 | ||
|
|
||
| | 메서드 | 설명 | 예시 / 비고 | | ||
| | --- | --- | --- | | ||
| | `filter(조건)` | 조건에 맞는 데이터만 걸러냄 | 짝수만 추출 | | ||
| | `map(변환 규칙)` | 데이터를 다른 형태로 변환 | User 객체에서 이름만 추출 | | ||
| | `sorted()` | 데이터 정렬 | 기본 오름차순, 사용자 지정 정렬 | | ||
| | `distinct()` | 중복된 데이터 제거 | | | ||
| | `limit(n)` | 데이터의 흐름에서 앞에서부터 n개의 데이터만 잘라냄 | | | ||
|
|
||
| --- | ||
|
|
||
| ### 최종 연산 | ||
|
|
||
| * **특징:** | ||
| * 가공된 데이터에서 최종 결과를 도출하는 마지막 단계 | ||
| * 실제 값이나 컬렉션을 반환하며, 최종 연산이 호출되어야 비로소 전체 스트림 연산이 실행됨 (지연 연산) | ||
| * 한 번 실행되면 스트림이 닫힘 | ||
|
|
||
| | 메서드 | 설명 | 예시 / 비고 | | ||
| | --- | --- | --- | | ||
| | `collect()` | 가공된 스트림 데이터를 List, Set, Map 등으로 묶어서 반환 | | | ||
| | `forEach()` | 스트림의 각 요소를 순회하며 출력하거나 작업을 수행함 | 반환값 없음 | | ||
| | `count()` | 스트림에 남은 데이터의 **총 개수** 반환 | | | ||
| | `reduce()` | 데이터를 하나씩 누적 계산 | 모든 숫자 합 계산 | | ||
| | `anyMatch(조건)` | 조건을 만족하는 데이터가 **하나라도 존재하는지** 확인 | boolean 반환 | | ||
|
|
||
| --- | ||
|
|
||
| ### 주의 사항 | ||
| * **스트림은 일회용임** → 한 번 최종 연산(`collect`, `count` 등)을 수행해서 결과를 얻으면 스트림은 닫혀서 다시 쓸 수 없다! 필요 시 재생성해야 함. | ||
| * **지연 연산(Lazy Evaluation)을 함** → 중간 연산(`filter`, `map`)을 많이 적어두어도 맨 끝에 최종 연산(`collect` 등)을 호출하지 않으면 계산이 아예 실행되지 않음. | ||
|
|
||
| # 객체 그래프 탐색 | ||
|
|
||
| > **객체 그래프 탐색** | ||
| > JPA나 ORM을 사용할 때 자주 등장하는 개념으로, 객체들 사이의 **연관 관계를 연쇄적으로 따라가며 조회하는 것** → 객체 안에 또 다른 객체가 있을 때 점(`.`)을 통해 타고 들어가면 된다. | ||
| > 실제 DB 조회 시점과 관련이 있기 때문에 중요함. | ||
| > 참조 변수를 통해 연관된 객체로 이동하는 행위를 말함. | ||
|
|
||
| **e.g.** | ||
| `comment.getUser().getName()` | ||
| * `comment`: 댓글 객체 | ||
| * `comment.getUser()`: 댓글을 작성한 사용자 (User) | ||
| * `comment.getUser().getName()`: 사용자의 이름 (Name) | ||
|
|
||
| --- | ||
|
|
||
| ### N+1 문제 | ||
| 객체 그래프 탐색은 필요할 때 가져온다는 지연 로딩(Lazy Loading) 특성을 가지기에 **N+1 문제**가 발생될 우려가 있음 → `fetch join`으로 해결 (한 번에 가져와서 객체 그래프 탐색 시 추가 쿼리 X) | ||
|
|
||
| * **현상:** 최초에 목록을 조회하는 쿼리를 한 번 날림 | ||
| * **문제:** 그 목록에 담긴 객체들을 하나씩 탐색할 때마다 연관된 데이터를 가져오기 위해 추가적인 쿼리가 N번 더 발생 | ||
| * **원인:** JPA 입장에서는 어디까지 탐색할지 미리 알 수 없음 → 처음에는 딱 요청한 것만 가져오고(프록시) 나중에 탐색을 시도할 때마다 쿼리를 날리기 때문에 발생하는 문제 | ||
|
|
||
| # @Valid vs @Validated | ||
|
|
||
| ### 차이점 | ||
|
|
||
| | 구분 | @Valid (자바 표준) | @Validated (스프링용) | | ||
| | --- | --- | --- | | ||
| | **소속** | Java/Jakarta EE 표준 규격(JSR-380) | Spring Framework | | ||
| | **동작 위치** | 주로 Controller의 파라미터 변환 시 동작 | Controller, Service, Repository 등 스프링 빈 어디서나 동작 | | ||
| | **동작 원리** | 스프링의 ArgumentResolver가 개입하여 검증 | 스프링의 AOP를 기반으로 메서드 호출을 가로채서 검증 | | ||
| | **그룹 지정** | 불가능 (무조건 전체 검증) | 가능 (상황에 따라 원하는 조건만 묶어서 검증) | | ||
| | **적용 대상** | 메서드 파라미터, 필드(객체 내부의 객체 검증 시) | 클래스 레벨, 메서드 파라미터 | | ||
| | **발생 예외 (Exception)** | `MethodArgumentNotValidException` | AOP 동작 시 `ConstraintViolationException` 발생 | | ||
|
|
||
| ### 공통점 | ||
| 객체에 설정된 제약 조건(`@NotNull`, `@Size` 등)을 확인하여 옳지 않은 데이터가 서버 내부로 들어오는 것을 막아줌 | ||
|
|
||
| --- | ||
|
|
||
| ## @Validated | ||
|
|
||
| > **@Validated** | ||
| > Spring 프레임워크에서 전용으로 제공하는 유효성 검증 어노테이션. | ||
| > 자바 표준인 `@Valid`의 모든 기능을 포함하면서, 추가적으로 **그룹화(Grouping)** 기능을 제공하여 특정 상황에 맞는 검증만 실행하라고 알려주는 신호. | ||
| > `@Valid`가 주로 웹(Controller) 계층에서만 동작한다면, `@Validated`는 스프링 빈으로 등록된 클래스라면 어디서든 유효성 검증이 가능해짐. | ||
|
|
||
| * **주 기능 : 그룹 유효성 검사 (Validation Groups)** | ||
| * ex) 회원 가입할 때와 회원 정보를 수정할 때 요구하는 데이터의 조건이 다를 경우 → 가입할 때는 비밀번호가 필수지만, 프로필 수정 시에는 비밀번호를 바꾸지 않을 수 있음 | ||
|
|
||
| ### @Validated 동작 흐름 | ||
| 1. 클라이언트 → Controller/Service 호출 | ||
| 2. Spring AOP 동작 (메서드 가로채기) | ||
| 3. 지정된 그룹 또는 파라미터 검증 | ||
| * **실패 :** `ConstraintViolationException` 또는 `MethodArgumentNotValidException` 발생 | ||
| * **성공 :** 대상 메서드의 비즈니스 로직 정상 수행 | ||
|
|
||
| ### 어노테이션 설명 (예시) | ||
| * `@NotBlank(groups = CreateGroup.class)` : 가입(Create)할 때만 필수 값으로 검증 | ||
| * `@NotNull(groups = UpdateGroup.class)` : 수정(Update)할 때만 null이 아닌지 검증 | ||
| * `@Validated(CreateGroup.class)` : 해당 그룹(Create)의 규칙만 검사하도록 지시함 | ||
|
|
||
| ### 클래스 레벨 검증 (Method Validation) | ||
| 클래스 위에 붙여 해당 클래스의 모든 메서드 파라미터에 대한 유효성 검사를 활성화할 수 있음 | ||
|
|
||
| ### 자바 @Valid와 비교했을 때 주의할 점 | ||
| `@Validated`를 클래스 레벨에 적용하여 발생하는 예외(`ConstraintViolationException`)와 컨트롤러 DTO 검증 시 발생하는 예외(`MethodArgumentNotValidException`)가 다름 | ||
| → 글로벌 예외 처리를 구현할 때 이 두 가지를 모두 처리해주어야 한다. | ||
|
|
||
| --- | ||
|
|
||
| ## @Valid | ||
|
|
||
| > **@Valid** | ||
| > Java Bean Validation의 기본 어노테이션. | ||
| > DTO, Entity, Method Parameter 등에서 검증을 시작하라고 Spring/Hibernate Validator에게 알려주는 신호. | ||
| > `@Valid`를 사용하면 객체 안에서 들어오는 값에 대해 검증이 가능해짐. | ||
|
|
||
| ### @Valid 동작 흐름 | ||
| 1. 클라이언트 → Controller 호출 | ||
| 2. Valid → Hibernate Validator 호출 | ||
| 3. DTO 필드 검증 | ||
| * **실패 :** `MethodArgumentNotValidException` 발생 | ||
| * **성공 :** Controller 비즈니스 로직 수행 | ||
|
|
||
| ### 검증 어노테이션 종류 | ||
|
|
||
| #### 문자열 검증 | ||
| | 어노테이션 | 설명 | | ||
| | --- | --- | | ||
| | `@NotBlank` | null X, 공백 제외 길이 > 0 | | ||
| | `@NotEmpty` | null X, 빈 문자열("") X | | ||
| | `@NotNull` | null X (타입 상관 없음) | | ||
| | `@Null` | 반드시 null 값 | | ||
|
|
||
| #### 최대/최솟값 검증 | ||
| | 어노테이션 | 설명 | | ||
| | --- | --- | | ||
| | `@DecimalMax` | 지정값 이하 (String 기반) | | ||
| | `@DecimalMin` | 지정값 이상 (String 기반) | | ||
| | `@Max` | 지정값 이하 (숫자 기반) | | ||
| | `@Min` | 지정값 이상 (숫자 기반) | | ||
|
|
||
| #### 범위 검증 | ||
| | 어노테이션 | 설명 | | ||
| | --- | --- | | ||
| | `@Positive` | 양수만 허용 | | ||
| | `@PositiveOrZero` | 0 이상 | | ||
| | `@Negative` | 음수만 허용 | | ||
| | `@NegativeOrZero` | 0 이하 | | ||
|
|
||
| *참고: `DecimalMax/Min`은 BigDecimal, BigInteger, String 등 정밀 수치용 / `Max/Min`은 기본 숫자형(int, long 등) 용* | ||
|
|
||
| #### 시간 값 검증 | ||
| | 어노테이션 | 설명 | | ||
| | --- | --- | | ||
| | `@Future` | 현재 시간보다 미래 | | ||
| | `@FutureOrPresent` | 현재 or 미래 | | ||
| | `@Past` | 현재 시간보다 과거 | | ||
| | `@PastOrPresent` | 현재 or 과거 | | ||
|
|
||
| #### Boolean 검증 | ||
| | 어노테이션 | 설명 | | ||
| | --- | --- | | ||
| | `@AssertTrue` | true 값만 허용 | | ||
| | `@AssertFalse` | false 값만 허용 | | ||
|
|
||
| #### 크기 검증 | ||
| | 어노테이션 | 설명 | | ||
| | --- | --- | | ||
| | `@Size` | 값의 길이가 min 이상 max 이하<br>→ `String`, `Collection`, `Map`, `Array` 에 적용 | | ||
|
|
||
| ### 자바 Valid 가 제공하지 않는 기능 | ||
| `@Valid`는 `javax.validation` 표준이지만, 그룹 유효성 검사를 지원하지 않음. | ||
| → 필요한 경우 `@Validated` 사용 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
Seohui/src/main/java/com/study/UMC10/domain/mission/converter/MissionConverter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,19 @@ | ||
| package com.study.UMC10.domain.mission.converter; | ||
|
|
||
| import com.study.UMC10.domain.mission.dto.response.MissionResponseDto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class MissionConverter { | ||
| public static <T> MissionResponseDto.Pagination<T> toPagination( | ||
| List<T> data, | ||
| Integer pageNumber, | ||
| Integer pageSize | ||
| ){ | ||
| return MissionResponseDto.Pagination.<T>builder() | ||
| .data(data) | ||
| .pageNumber(pageNumber) | ||
| .pageSize(pageSize) | ||
| .build(); | ||
| } | ||
| } |
15 changes: 14 additions & 1 deletion
15
Seohui/src/main/java/com/study/UMC10/domain/mission/dto/request/MissionRequestDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,17 @@ | ||
| package com.study.UMC10.domain.mission.dto.request; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| public class MissionRequestDto { | ||
| } | ||
|
|
||
| /* | ||
| * NOTE: | ||
| * GET 요청에 Request Body를 사용하지 않지만 미션 요구사항에 따라 Request Body로 userId를 전달받음 | ||
| */ | ||
| @Schema(description = "진행 중인 미션 조회 요청") | ||
| public record GetMyMissionsDto( | ||
| @Schema(description = "조회할 유저 ID", example = "1") | ||
| Long userId | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요청 본문으로 |
||
| ) { | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
객체 그래프 탐색과 N+1 문제를 함께 연결한 점이 좋습니다. 추가로
fetch join만 해결책으로 고정하기보다@EntityGraph, batch size, DTO projection이 각각 어떤 조회 목적에 적합한지도 비교하면 JPA 조회 전략을 더 입체적으로 이해할 수 있습니다.