|
| 1 | +- Page와 Slice |
| 2 | + |
| 3 | + Pageable은 페이징 요청 정보를 담는 인터페이스이다. |
| 4 | + |
| 5 | + → 결국, 페이징을 진행할 데이터의 원본은 데이터베이스에 있다. |
| 6 | + |
| 7 | + JPA는 Pageable 정보를 기반으로 DB에 쿼리를 날리고, 결과를 다시 특정 객체로 반환해준다. |
| 8 | + |
| 9 | + 이때 결과를 담아주는 객체가 **Page**와 **Slice**이다. |
| 10 | + |
| 11 | + - 객체 상속 관계 |
| 12 | + |
| 13 | +  |
| 14 | + |
| 15 | + - Slice |
| 16 | + - 다음 페이지가 있는지만 알려주는 가벼운 페이징 결과 객체 |
| 17 | + - **SliceImpl**구현 클래스를 통해 객체를 생성하고 동작을 수행한다. |
| 18 | + - 생성 방식 |
| 19 | + - **List<T>content + pageable + hasNext** |
| 20 | + |
| 21 | +  |
| 22 | + |
| 23 | + ```java |
| 24 | + // 현재 페이지의 데이터 |
| 25 | + List<String> content = Arrays.asList("item1", "item2", "item3"); |
| 26 | + |
| 27 | + // Pageable 정보 (0번 페이지, 3개씩 조회) |
| 28 | + Pageable pageable = PageRequest.of(0, 3); |
| 29 | + |
| 30 | + // 다음 페이지가 있다고 가정 |
| 31 | + boolean hasNext = true; |
| 32 | + |
| 33 | + // SliceImpl 생성 |
| 34 | + Slice<String> slice = new SliceImpl<>(content, pageable, hasNext); |
| 35 | + ``` |
| 36 | + |
| 37 | + - hasNext 값을 인자로 받는다. |
| 38 | + - 전체 데이터 개수를 알기 위한 별도의 count(*) 쿼리가 필요하지 않다. |
| 39 | + - **전체 페이지 수를 알 필요 없는 무한 스크롤과 같은 경우는 Slice를 사용하는 것이 적합하다.** |
| 40 | + - 전체 페이지를 모르는데 hasNext()를 판별할 수 있는 이유 |
| 41 | + |
| 42 | + 기존의 페이지 사이즈보다 한 개를 더해서 불러온다면 |
| 43 | + 다음 페이지에 데이터가 있는지에 대한 여부를 확인 가능하다. |
| 44 | + |
| 45 | + ⇒JPA에서는 쿼리를 보낼 때 PageRequest가 요구하는 |
| 46 | + 페이지 사이즈에 +1을 하여 쿼리를 조회한다. |
| 47 | + |
| 48 | + - -**List<T>content** |
| 49 | + |
| 50 | +  |
| 51 | + |
| 52 | + - pageable이 필요 없는 경우에 사용한다. |
| 53 | + - hasNext를 false로 이용한다. |
| 54 | + |
| 55 | + ```java |
| 56 | + this(content, Pageable.unpaged(), false); |
| 57 | + |
| 58 | + ``` |
| 59 | + |
| 60 | + - Page |
| 61 | + - Slice의 모든 기능 + 전체 페이지 수, 총 데이터 개수 제공 |
| 62 | + - **PageImpl** 구현 클래스를 통해 객체를 생성하고 동작을 수행한다. |
| 63 | + - hasNext(): 다음 페이지가 있는지에 대한 여부 |
| 64 | + - isLast(): 현재 페이지가 마지막인지에 대한 여부 |
| 65 | + - getTotalPages(), getTotalElements(): 전체 데이터 개수에 대한 정보 |
| 66 | + - 생성 방식 |
| 67 | + - **List<T>content + pageable + total** |
| 68 | + |
| 69 | +  |
| 70 | + |
| 71 | + ```java |
| 72 | + // 현재 페이지의 데이터 |
| 73 | + List<String> content = Arrays.asList("item1", "item2", "item3"); |
| 74 | + |
| 75 | + // Pageable 정보 (0번 페이지, 3개씩 조회) |
| 76 | + Pageable pageable = PageRequest.of(0, 3); |
| 77 | + |
| 78 | + // 전체 데이터 개수가 100개 |
| 79 | + long total = 100 |
| 80 | + |
| 81 | + // Page 생성 |
| 82 | + Page<String> page = new PageImple<>(content, pageable, total); |
| 83 | + ``` |
| 84 | + |
| 85 | + - 전체 데이터 개수를 알아야 하기 때문에, |
| 86 | + Spring Data JPA는 내부적으로 count(*) 쿼리를 별도로 실행하여 |
| 87 | + 전체 데이터 수를 조회한다. |
| 88 | + |
| 89 | + - **List<T>content** |
| 90 | + |
| 91 | + -  |
| 92 | + ```java |
| 93 | + List<String> content = userRepository.findAll() // 전체 데이터 |
| 94 | + Page<User> page = new PageImpl<>(content); // 전체를 Page로 감싸기 |
| 95 | + ``` |
| 96 | + |
| 97 | + - 페이지 정보 없이 단순히 리스트만 주어진 경우에 사용하는 간단한 생성자 |
| 98 | + - 총 개수는 content.size() 로 대체한다. |
| 99 | + |
| 100 | + - 상황별 사용 |
| 101 | + |
| 102 | + |
| 103 | + | 반환타입 | 설명 | 적합한 상황 | |
| 104 | + | --- | --- | --- | |
| 105 | + | Page<T> | 전체 개수(count(*))와 전체 페이지 수를 함께 제공 | 페이지 번호와 전체 페이지 수 표시가 필요한 페이징 (예: 게시판) | |
| 106 | + | Slice<T> | 다음 페이지 유무만 판단 (count(*)는 실행하지 않음) | 무한 스크롤, 성능이 중요한 경우(ex. 모바일 피드) | |
| 107 | + - 단점 |
| 108 | + - Slice는 오프셋이 큰 경우에 DB 성능이 저하될 가능성이 있다.’ |
| 109 | + - Page는 COUNT 쿼리 비용이 클 수 있다. |
| 110 | + |
| 111 | + |
| 112 | +- Java stream API |
| 113 | + |
| 114 | + Stream API: |
| 115 | + 람다식을 이용한 기술 중에 하나로 데이터 소스를 조작 및 가공, 변환하여 |
| 116 | + 원하는 값으로 반환해주는 인터페이스이다. |
| 117 | + |
| 118 | + java.util.stream에 있다. |
| 119 | + |
| 120 | + 배열의 각 요소를 개발자가 직접 컬렉션 외부로 꺼내올 필요 없이, |
| 121 | + 컬렉션 내부에서 어떻게 처리할지만 기술하면 된다. |
| 122 | + 이러한 특성으로 인해 Streams API는 내부 반복자라고 부른다. |
| 123 | + |
| 124 | + - 람다식이란? |
| 125 | + |
| 126 | + 익명 함수라고도 한다. |
| 127 | + |
| 128 | + 함수를 하나의 식으로 표현한 인터페이스이다. 메소드의 이름이 없다. |
| 129 | + |
| 130 | + |
| 131 | +- 특징 |
| 132 | + - Stream은 원본 데이터를 변경하지 않는다. |
| 133 | + - 재사용이 불가능하여서 일회용으로 사용된다. |
| 134 | + - 내부 반복으로 작업을 처리한다. |
| 135 | + |
| 136 | + - 과정 |
| 137 | + - Stream 생성 → 중간 연산 → 최종 연산 |
| 138 | + - 객체.Stream생성().중간연산.최종연산 ⇒ 이런 식으로 작성 |
| 139 | + |
| 140 | + - 중간 연산 종류 |
| 141 | + - |
| 142 | + | Stream 필터 | filter(), distinct() | |
| 143 | + | --- | --- | |
| 144 | + | Stream 변환 | map(), flatMap() | |
| 145 | + | Stream 제한 | limit(), skip() | |
| 146 | + | Stream 정렬 | sorted() | |
| 147 | + | Stream 연산 결과 확인 | peek() | |
| 148 | + |
| 149 | + - 최종 연산 종류 |
| 150 | + - |
| 151 | + | 요소의 출력 | forEach() | |
| 152 | + | --- | --- | |
| 153 | + | 요소의 검색 | findFirst(), findAny() | |
| 154 | + | 요소의 검사 | anyMatch(), allMatch(), noneMatch() | |
| 155 | + | 요소의 통계 | count(), min(), max() | |
| 156 | + | 요소의 연산 | sum(), average() | |
| 157 | + | 요소의 수집 | collect() | |
| 158 | + - 사용예시 |
| 159 | + - 대량 데이터를 한 번만 반복 처리할 때는 parallelStream이 유리 |
| 160 | + - 작은 데이터에서는 for문이 빠르다. |
| 161 | + |
| 162 | + ```java |
| 163 | + void mapping_and_sorting(){ |
| 164 | + List<String> names = Arrays.asList("John", "Jane", "Tom", "Jerry"); |
| 165 | + |
| 166 | + List<String> sortedNames = names.stream() |
| 167 | + .map(String::toUpperCase) // 중간 연산: 대문자로 변환 |
| 168 | + .sorted() // 중간 연산: 알파벳 순으로 정렬 |
| 169 | + .toList(); |
| 170 | + |
| 171 | + assertEquals("JANE", sortedNames.get(0)); |
| 172 | + assertEquals("JERRY", sortedNames.get(1)); |
| 173 | + assertEquals("JOHN", sortedNames.get(2)); |
| 174 | + assertEquals("TOM", sortedNames.get(3)); |
| 175 | + } |
| 176 | + ``` |
| 177 | + |
| 178 | + - 장단점 |
| 179 | + - 최종 연산을 누락하는 경우 그 스트림은 작업을 처리하지 않고 무시된다. |
| 180 | + - 재사용 스트림 문제 |
| 181 | + - 다음 코드에서 이미 사용한 스트림을 사용하려고 하면 작동하지 않는다. |
| 182 | + - 무한 스트림 생성 |
| 183 | + - iterate() 연산을 사용하면 무한히 생성될 수 있다. |
| 184 | + - 반복문 없이 가독성 높은 코드 작성 가능 |
| 185 | + - 병렬 처리 가능 |
| 186 | + - parallelStream() 사용하면 중간 연산 병렬 처리 가능 |
| 187 | + |
| 188 | +- 객체 그래프 탐색 |
| 189 | + |
| 190 | + 객체는 상속, 연관 관계만 맺어져 있다면 자유롭게 그래프를 탐색 가능해야 한다. |
| 191 | + |
| 192 | + JPA는 지연로딩을 통해 객체를 사용하는 시점까지 DB 작업을 미룬다. |
| 193 | + |
| 194 | + - 장단점 |
| 195 | + - SQL 직접 작성과 다르게 조인의 제약에서 벗어나 |
| 196 | + 논리적인 도메인 모델 구조에 따라 데이터를 조회 가능 |
| 197 | + - 객체 그래프를 무분별하게 탐색할 경우, 하이버네이트 등에서 N+1 문제 발생 가능 |
| 198 | + - SQL과 차이 |
| 199 | + - DB는 지정된 테이블만 조회가 가능, 모든 객체 그래프를 탐색하기 어렵다. |
| 200 | + - 자유로운 탐색이 가능하다. |
| 201 | + |
| 202 | + - @Valid vs @Validated |
| 203 | + |
| 204 | + 둘 다 유효성 검증을 위한 어노테이션이다. |
| 205 | + |
| 206 | + - @Valid |
| 207 | + - Bean Validator를 통해 객체의 데이터 유효성 검증을 지시하는 어노테이션 |
| 208 | + - 구현체가 따로 없다 ⇒ Hibernate Validator 사용 |
| 209 | + - 동작 과정 |
| 210 | + - 요청이 들어오면 Dispatcher Servlet에서 |
| 211 | + 요청에 맞는 컨트롤러에 요청을 전달합니다. |
| 212 | + - 전달 과정에서 컨트롤러 메소드의 객체를 전달해주는 HandlerMethodArgumentResolver 동작 |
| 213 | + @Valid 역시 HandlerMethodArgumentResolver 의해 처리가 됩니다. |
| 214 | + |
| 215 | + **ArgumentResolver : @RequestBody가 붙은 데이터를 Json 포멧으로 변경** |
| 216 | + |
| 217 | + - @**Valid**로 시작하는 어노테이션이 붙어있으면 |
| 218 | + dispathcher servlet 단에서 검사를 진행하는 식으로 동작 |
| 219 | + |
| 220 | + **⇒ 주로 Request Body에서 검증하는데 많이 사용된다.** |
| 221 | + |
| 222 | + - Spring에서 @Valid는 기본적으로 Controller에서만 동작하도록 설계되어 있다 |
| 223 | + **다른 계층에서 검증하기 위해서는 @Validated와 결합**하여 사용합니다. |
| 224 | + |
| 225 | + - 장단점 |
| 226 | + - 자바 표준 |
| 227 | + - 컨트롤러에서만 사용 가능 |
| 228 | + |
| 229 | + - @Validation |
| 230 | + - Controller 뿐만 아니라 다른 계층에서도 데이터 유효성 검증을 해야하는 경우 |
| 231 | + 메소드 요청을 가로채서 유효성 검증을 해주는 **Spring AOP 기반 기능** |
| 232 | + - 유효성 검증이 필요한 클래스에 @Validated를 붙이고, |
| 233 | + 유효성 검증을 할 파라미터에 @Valid를 붙이면 동작하게 됩니다. |
| 234 | + |
| 235 | + ```java |
| 236 | + @Service |
| 237 | + @Validated |
| 238 | + public class Service { |
| 239 | + |
| 240 | + public void doSomething(@Valid Request request) { |
| 241 | + ... |
| 242 | + } |
| 243 | + } |
| 244 | + ``` |
| 245 | + |
| 246 | + - Spring의 기능이기 때문에 Bean이면 모두 유효성 검증이 가능하다. |
| 247 | + - MethodValidationInterceptor에 Validator이 의존성 주입으로 들어가므로 |
| 248 | + @Valid와 똑같이 Hibernate Validator를 사용하기 때문에 |
| 249 | + Bean Validation을 그대로 이용할 수 있게 됩니다. |
| 250 | + |
| 251 | + - 동작 과정 |
| 252 | + - @Validated를 클래스에 선언하면 해당 클래스에 유효성 검증을 위한 |
| 253 | + 인터셉터인 MethodValidationInterceptor가 등록됩니다. |
| 254 | + - 해당 클래스의 메소드가 호출 될 때 AOP가 확인을 하고 |
| 255 | + 요청을 중간에 가로채어 유효성 검증을 진행하게 됩니다. |
| 256 | + - 장단점 |
| 257 | + - 그룹 검증 |
| 258 | + - 모든 Bean에서 유효성 검증 |
| 259 | + - 스프링 환경에서만 가능 |
| 260 | + |
| 261 | + |
0 commit comments