|
| 1 | +- Page와 Slice |
| 2 | + |
| 3 | + Spring Data JPA에서 페이징 구현을 위해 제공하는 두 가지 인터페이스가 바로 Page와 Slice |
| 4 | + |
| 5 | + ```java |
| 6 | + //MemberRepository |
| 7 | + |
| 8 | + ... |
| 9 | + |
| 10 | + Page<Member> findPageBy(Pageable pageable); |
| 11 | + Slice<Member> findSliceBy(Pageable pageable); |
| 12 | + |
| 13 | + ... |
| 14 | + ``` |
| 15 | + |
| 16 | + ## Page |
| 17 | + |
| 18 | + - Page는 Slice를 상속했기 때문에 현재 페이지의 데이터에 더해서 전체 데이터의 개수와 전체 페이지 수를 모두 알고 있다 |
| 19 | + - 조회(SELECT) 쿼리 이후 전체 데이터 개수를 조회(SELECT COUNT)하는 쿼리가 한 번 더 실행 |
| 20 | + - 전체 페이지 개수나 데이터 개수가 필요한 경우 유용 (게시판 형태) |
| 21 | + |
| 22 | + ## Slice |
| 23 | + |
| 24 | + - 현재 페이지에 포함된 데이터 목록과 다음 페이지가 있는지 여부에 대한 정보만 제공 |
| 25 | + - 조회 쿼리 한 번만 실행 |
| 26 | + - 사용자가 요청한 데이터 개수(limit)보다 하나 더 많은 데이터를 요청해서 다음 데이터 존재의 여부를 판단 |
| 27 | + - COUNT 실행되지 않음 |
| 28 | + - COUNT 쿼리가 생략되므로 대용량 데이터 조회 시 성능 상 유리 |
| 29 | + - 총 데이터 개수가 필요 없는 상황에서 유용 (무한 스크롤) |
| 30 | + |
| 31 | +- Java stream API |
| 32 | + |
| 33 | + ## Java stream API |
| 34 | + |
| 35 | + 데이터의 묶음을 반복문 없이 함수형 프로그래밍 방식으로 간결하게 처리 가능하게 해준다 |
| 36 | + |
| 37 | + ### 과정 |
| 38 | + |
| 39 | + ```java |
| 40 | + List<String> words = Arrays.asList("show", "me", "the", "money", "money"); |
| 41 | + |
| 42 | + List<String> distinctWords = |
| 43 | + words.stream() // 1. Stream 생성 |
| 44 | + .filter(word -> word.length() > 3) // 2. 중간연산 |
| 45 | + .distinct() // 2. 중간연산 |
| 46 | + .collect(Collectors.toList()); // 3. 최종연산 |
| 47 | + ``` |
| 48 | + |
| 49 | + 1. 생성 |
| 50 | + - 컬렉션이나 배열 등의 데이터를 스트림 객체로 만든다 |
| 51 | + - .stream |
| 52 | + 2. 중간 연산 |
| 53 | + - 데이터를 조건에 맞게 걸러내거나 변형 |
| 54 | + - 연산 |
| 55 | + - .filter(조건) : 조건이 참인 것만 통과 |
| 56 | + - .distinct() : 중복 요소 제거 |
| 57 | + - .map(Function) : 데이터를 함수를 통해서 내가 원하는 다른 형태로 변환 추출 |
| 58 | + - .limit(size) : 스트림의 맨 앞부터 size만큼만 가져오기 |
| 59 | + - sorted(condition) : 조건에 맞게 데이터 정렬, 없으면 오름차순 |
| 60 | + 3. 최종 연산 |
| 61 | + - 가공된 데이터를 최종적인 결과물로 변환 |
| 62 | + - 연산 |
| 63 | + - .collect(.toList) : 스트림을 통과하며 가공된 데이터들을 List, Set, Map 같은 컬렉션 형태로 다시 포장 |
| 64 | + - .count() : 최종 결과물에 데이터가 몇 개 남아있는지를 계산, long 리턴 |
| 65 | + - .forEach(작업) : 스트림을 통과한 데이터들을 차례대로 하나씩 꺼내서 특정 작업을 실행, 변수에 담아서 반환X |
| 66 | + |
| 67 | + ### 특징 |
| 68 | + |
| 69 | + - 반복문을 사용하지 않고, 선언적으로 데이터 흐름을 표현 |
| 70 | + - 중간 연산은 최종 연산이 호출되기 전까지 실제로 실행되지 않아 성능을 향상 |
| 71 | + - 원본을 바꾸지 않음 |
| 72 | +- 객체 그래프 탐색 |
| 73 | + |
| 74 | + ## 객체 그래프 탐색 |
| 75 | + |
| 76 | + 객체들이 서로 참조(Reference)를 통해 연결되어 있는데, 이 연결된 것이 그래프 같다고 해서 객체 그래프라고 함 |
| 77 | + |
| 78 | + 그리고 이 객체 그래프를 따라가면서 찾는 것이 **객체 그래프 탐색** |
| 79 | + |
| 80 | + ```java |
| 81 | + String univName = menu.getCafeteria().getUniversity().getName(); |
| 82 | + ``` |
| 83 | + |
| 84 | + ### JPA에서 왜 중요함? |
| 85 | + |
| 86 | + RDB에서는 객체처럼 다른 테이블의 데이터를 마음대로 가지고 올 수 없고 JOIN 해야함따라서 SQL을 어떻게 짰느냐에 따라 자바 코드에서 객체를 탐색할 수 있는 범위가 한정됨 |
| 87 | + |
| 88 | + **→ JPA가 지연 로딩을 통해 해결!!** |
| 89 | + |
| 90 | + ```java |
| 91 | + // 1. DB에서 메뉴 번호 1번을 가져옴 (메뉴 테이블만 SELECT) |
| 92 | + Menu menu = menuRepository.findById(1L).orElseThrow(); |
| 93 | + |
| 94 | + // 2. 이때 식당(Cafeteria) 객체는 진짜 객체가 아니라 '가짜(프록시) 객체'가 들어있음 |
| 95 | + Cafeteria cafeteria = menu.getCafeteria(); |
| 96 | + |
| 97 | + // 3. 실제로 식당의 이름이 필요한 순간 |
| 98 | + // JPA가 몰래 DB에 SELECT 쿼리를 날려서 식당 데이터를 가져옴 (지연 로딩) |
| 99 | + System.out.println(cafeteria.getName()); |
| 100 | + ``` |
| 101 | + |
| 102 | + - 이 때 반복문으로 인한 N+1 문제를 조심해야함 |
| 103 | + - fetch join!! |
| 104 | +- @Valid vs @Validated |
| 105 | + |
| 106 | + ## @Valid |
| 107 | + |
| 108 | + - java에서 제공 |
| 109 | + - 주로 밑 예시처럼 Controller에서 유효성 검증이 필요한 파라미터에 붙여서 사용 |
| 110 | + |
| 111 | + ```java |
| 112 | + @PostMapping("/users") |
| 113 | + public ResponseEntity<?> createUser(@Valid @RequestBody UserDto userDto) { |
| 114 | + // userDto 내부의 @NotBlank, @NotNull 등이 검증됨 |
| 115 | + return ResponseEntity.ok().build(); |
| 116 | + } |
| 117 | + ``` |
| 118 | + |
| 119 | + ## @Validated |
| 120 | + |
| 121 | + - spring 제공 |
| 122 | + - 계층과 무관하게 Spring Bean이라면 유효성 검증을 진행 |
| 123 | + - valid 기능을 모두 제공하면서 추가 기능 제공 |
| 124 | + - 그룹 검증 지원 |
| 125 | + - 같은 DTO 객체라도 로직(생성, 수정 등)에 따라 검증 조건이 다를 때 사용 |
| 126 | + |
| 127 | + ```java |
| 128 | + // 1. 마커 인터페이스(그룹) 생성 |
| 129 | + public interface CreateGroup {} |
| 130 | + public interface UpdateGroup {} |
| 131 | + |
| 132 | + public class UserDto { |
| 133 | + @NotNull(groups = {UpdateGroup.class}) // 수정할 때만 필수 |
| 134 | + private Long id; |
| 135 | + |
| 136 | + @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}) // 생성, 수정 모두 필수 |
| 137 | + private String name; |
| 138 | + } |
| 139 | + |
| 140 | + // 2. 컨트롤러에서 사용할 그룹 지정 |
| 141 | + @PostMapping("/users") |
| 142 | + public ResponseEntity<?> createUser(@Validated(CreateGroup.class) @RequestBody UserDto dto) { |
| 143 | + // id 필드는 검증하지 않고, name 필드만 검증함 |
| 144 | + return ResponseEntity.ok().build(); |
| 145 | + } |
| 146 | + ``` |
| 147 | + |
| 148 | + - 컨트롤러 클래스 파라미터 단일 검증 |
| 149 | + - dto가 아닌 @RequestParam, @PathVariable 검증 시 사용 |
| 150 | + - 클래스 위에 @Validated 선언 |
| 151 | + |
| 152 | + ```java |
| 153 | + @RestController |
| 154 | + **@Validated // 컨트롤러 클래스 상단** |
| 155 | + @RequestMapping("/users") |
| 156 | + public class UserController { |
| 157 | + |
| 158 | + @GetMapping("/{id}") |
| 159 | + public ResponseEntity<?> getUser( |
| 160 | + @PathVariable **@Min(1)** Long id // id가 1 이상인지 단일 검증 |
| 161 | + ) { |
| 162 | + return ResponseEntity.ok().build(); |
| 163 | + } |
| 164 | + } |
| 165 | + ``` |
| 166 | + |
| 167 | + |
| 168 | + → 일반적인 dto 검증은 @Valid 권장 |
0 commit comments