Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- Spring Boot
- JDBC
- Greedy
- 알고리즘
- pointcut
- java
- 그리디
- Exception
- SpringBoot
- kotlin
- QueryDSL
- 스프링
- springdatajpa
- http
- transaction
- Android
- 자바
- db
- 김영한
- Thymeleaf
- AOP
- jpa
- JPQL
- 스프링 핵심 원리
- 백준
- spring
- 인프런
- 스프링 핵심 기능
- Servlet
- Proxy
Archives
- Today
- Total
개발자되기 프로젝트
컬렉션 조회 최적화 본문
컬렉션인 일대다 관계를 조회하고, 최적화하자.
일대다 조회는 DB입장에서 data가 뻥튀기 된다.
예를들어 하나의 주문(orderA)에 item( #1, #2, #3)이 3개라면
data는 1개가 아니라 3개가 된다.
orderA - #1, orderA - #2, orderA-#3
1. 엔티티 직접 노출 : V1
- get을 통해 member, orderItem, Delivery 프록시를 강제로 초기화 시킨다.
@RestController
@RequiredArgsConstructor
public class OrderApiController {
private final OrderRepository orderRepository;
@GetMapping("/api/v1/orders")
public List<Order> ordersV1(){
List<Order> all = orderRepository.findAllByString(new OrderSearch());
for (Order order : all) {
order.getMember().getName();
order.getDelivery().getAddress();
List<OrderItem> orderItems = order.getOrderItems();
orderItems.stream().forEach(o -> o.getItem().getName());
}
return all;
}
}
- 반환된 결과이다.
[
{
"id": 4,
"member": {
"id": 1,
"name": "userA",
"address": {
"city": "서울",
"street": "송파",
"zipcode": "232"
}
},
"orderItems": [
{
"id": 6,
"item": {
"id": 2,
"name": "JPA1 BOOK",
"price": 10000,
"stockQuantity": 99,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 10000,
"count": 1,
"totalPrice": 10000
},
{
"id": 7,
"item": {
"id": 3,
"name": "JPA2 BOOK",
"price": 20000,
"stockQuantity": 98,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 20000,
"count": 2,
"totalPrice": 40000
}
],
"orderDate": "2021-08-23T23:33:07.505188",
"status": "ORDER",
"totalPrice": 50000
},
{
"id": 11,
"member": {
"id": 8,
"name": "userB",
"address": {
"city": "부산",
"street": "강남남",
"zipcode": "222"
}
},
"orderItems": [
{
"id": 13,
"item": {
"id": 9,
"name": "spring1 BOOK",
"price": 20000,
"stockQuantity": 197,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 10000,
"count": 3,
"totalPrice": 30000
},
{
"id": 14,
"item": {
"id": 10,
"name": "spring2 BOOK",
"price": 40000,
"stockQuantity": 296,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 20000,
"count": 4,
"totalPrice": 80000
}
],
"orderDate": "2021-08-23T23:33:07.589062",
"status": "ORDER",
"totalPrice": 110000
}
]
- 하지만 엔티티를 직접 노출하는건 좋지 않아..
2. V2 : DTO로 변환
- DTO를 반환할 때 안에 엔티티가 있으면 안됨
- 어쨌든 DTO안에 엔티티가 있으면 외부로 엔티티가 노출되는 것과 같음!
@Getter
static class OrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItem> orderItems;
public OrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
order.getOrderItems().stream().forEach(o -> o.getItem().getName());//강제 초기화.
orderItems = order.getOrderItems();
}
}
- OrderItem 엔티티가 DTO로 감싸여서 외부로 노출됨..ㅜ
[
{
"orderId": 4,
"name": "userA",
"orderDate": "2021-08-23T23:52:55.458853",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"street": "송파",
"zipcode": "232"
},
"orderItems": [
{
"id": 6,
"item": {
"id": 2,
"name": "JPA1 BOOK",
"price": 10000,
"stockQuantity": 99,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 10000,
"count": 1,
"totalPrice": 10000
},
{
"id": 7,
"item": {
"id": 3,
"name": "JPA2 BOOK",
"price": 20000,
"stockQuantity": 98,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 20000,
"count": 2,
"totalPrice": 40000
}
]
},
{
"orderId": 11,
"name": "userB",
"orderDate": "2021-08-23T23:52:55.506581",
"orderStatus": "ORDER",
"address": {
"city": "부산",
"street": "강남남",
"zipcode": "222"
},
"orderItems": [
{
"id": 13,
"item": {
"id": 9,
"name": "spring1 BOOK",
"price": 20000,
"stockQuantity": 197,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 10000,
"count": 3,
"totalPrice": 30000
},
{
"id": 14,
"item": {
"id": 10,
"name": "spring2 BOOK",
"price": 40000,
"stockQuantity": 296,
"categories": null,
"author": null,
"isbn": null
},
"orderPrice": 20000,
"count": 4,
"totalPrice": 80000
}
]
}
]
- 따라서 엔티티에 대한 의존을 완전히 끊어야함.
- 이 사례의 경우 orderItem을 변경하면 결국 DTO가 변경되고 결국 API Spec이 변경된다 ㅜ
- 완전히 DTO로 바꿔야함.
- OrderItem도 DTO로 바꾸자!!!
@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2(){
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<OrderDto> collect = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
return collect;
}
@Getter
static class OrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItemDto> orderItems;
public OrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
order.getOrderItems().stream().forEach(o -> o.getItem().getName());//강제 초기화.
orderItems = order.getOrderItems().stream().map(orderItem -> new OrderItemDto(orderItem)).collect(Collectors.toList());
}
}
@Getter
static class OrderItemDto{
private String itemName;
private int orderPrice;
private int count;
public OrderItemDto(OrderItem orderItem) {
itemName = orderItem.getItem().getName();
orderPrice = orderItem.getOrderPrice();
count = orderItem.getCount();
}
}
- ㅋㅋㅋ내가 원하는 정보만 출력되었다.
[
{
"orderId": 4,
"name": "userA",
"orderDate": "2021-08-24T00:01:53.81268",
"orderStatus": "ORDER",
"address": {
"city": "서울",
"street": "송파",
"zipcode": "232"
},
"orderItems": [
{
"itemName": "JPA1 BOOK",
"orderPrice": 10000,
"count": 1
},
{
"itemName": "JPA2 BOOK",
"orderPrice": 20000,
"count": 2
}
]
},
{
"orderId": 11,
"name": "userB",
"orderDate": "2021-08-24T00:01:53.868504",
"orderStatus": "ORDER",
"address": {
"city": "부산",
"street": "강남남",
"zipcode": "222"
},
"orderItems": [
{
"itemName": "spring1 BOOK",
"orderPrice": 10000,
"count": 3
},
{
"itemName": "spring2 BOOK",
"orderPrice": 20000,
"count": 4
}
]
}
]
- 근데 참고로 이렇게 루트에 json배열로 하지 말고 data로 한번 감싸서 나가야 다른 데이터 추가가 가능.
@Data
@AllArgsConstructor
static class Result<T>{
private T data;
}
@GetMapping("/api/v2/orders")
public Result ordersV2(){
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<OrderDto> collect = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
return new Result(collect);
- 요렇게 data안에 결과를 넣어줘야 json구성이 다른 다른 데이터 추가가 가능함!
- 와 근데 쿼리가 너무 많이나가......order는 2개인데 쿼리가 11개나 나가.. 끔찍..
- 최적화 절실함..
3. GitHub : 210823 collection조회 최적화
'인프런 > [인프런] Springboot와 JPA활용 2' 카테고리의 다른 글
컬렉션 조회 최적화 :페이징 & batch_fetch (0) | 2021.08.24 |
---|---|
컬렉션 조회 최적화 : fetch join 적용 (0) | 2021.08.24 |
지연 로딩과 조회 성능 최적화(JPA에서 DTO 바로 조회) (0) | 2021.08.23 |
지연 로딩과 조회 성능 최적화(FETCH JOIN) (0) | 2021.08.22 |
지연로딩과 성능최적화(DTO, N+1) (0) | 2021.08.22 |
Comments