티스토리 뷰
package jpabook2.jpashop2.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
@Id @GeneratedValue
@Column(name= "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY) //order 입장에서는 여러개의 order에 하나의 회원이다.
@JoinColumn(name = "member_id") //FK를 지정해준다.
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) //cascade는 여러개를 한 번에 해줌.. 무슨말인지 잘모르겠다
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status; //주문상태 [ORDER, CANCEL]
//==연관관계 메서드==//
public void setMember(Member member){
this.member =member;
member.getOrders().add(this);
}
public void addOrderItem(OrderItem orderItem){
orderItems.add(orderItem);
orderItem.setOrder(this);
}
public void setDelivery(Delivery delivery){
this.delivery = delivery;
delivery.setOrder(this);
}
//==생성 메서드==//
public static Order createOrder(Member member,Delivery delivery, OrderItem... orderItems){
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for(OrderItem orderItem : orderItems){
order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
}
//==비지니스 로직 ==//
/**
* 주문 취소
*/
public void cancel(){
if(delivery.getStatus() == DeliveryStatus.COMP){
throw new IllegalStateException("이미 배송완료 된 상품은 취소가 불가능합니다.");
}
this.setStatus(OrderStatus.CANCEL);
for(OrderItem orderItem : orderItems){
orderItem.cancel();
}
}
//==조회 로직==//
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice(){
/*
int totalPrice = 0;
for(OrderItem orderItem : orderItems){
totalPrice += orderItem.getTotalPrice();
}
*/
return orderItems.stream()
.mapToInt(OrderItem::getTotalPrice)
.sum();
}
}
order 객체의 일대다 관계인 orderItems을 꺼내보려한다.
V1
/*엔티티 그대로 노출*/
@GetMapping("/api/v1/orders")
public List<Order> ordersV1(){
List<Order> all = orderRepository.findAllByString(new OrderSearch());
for (Order order : all) {
order.getMember().getName();
order.getDelivery().getStatus();
List<OrderItem> orderItems = order.getOrderItems();
orderItems.stream().forEach(o -> o.getItem().getName()); //자동 초기화
}
return all;
}
- 조회 후에 반복문으로 LAZY 부분을 강제 초기화 시켰다.
- 엔티티가 그대로 노출된다. 바꿔야 한다.
V2
@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;
}
@Data
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();
orderItems = order.getOrderItems();
}
}
- 이렇게 하면 DTO로 사용하게 된다. 하지만 DTO를 잘보면 OrderItem은 엔티티다. 이렇게 사용하면 또 문제가 된다. 엔티티를 그대로 사용하는 것으로 api스펙이 바뀌면 문제가 또 된다. 이것마저 DTO로 변경해야한다.
@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2(){
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<OrderDto> result = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
return result;
}
@Data
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();
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();
}
}
이렇게 하면 해결된다. 하지만 LAZY로 인해 아직 조회 쿼리가 많이 날아간다..
V3
@GetMapping("/api/v3/orders")
public List<OrderDto> ordersV3(){
List<Order> orders = orderRepository.findAllWithItem();
for (Order order : orders) {
System.out.println("order ref = " + order + " id = " + order.getId());
}
List<OrderDto> result = orders.stream().map(o -> new OrderDto(o)).collect(Collectors.toList());
return result;
}
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o " +
"join fetch o.member m " +
"join fetch o.delivery d " +
"join fetch o.orderItems oi " +
"join fetch oi.item i ", Order.class).getResultList();
}
레퍼지토리에 fetch join을 해주었다. 실제로 쿼리를 조인하면 일대다 관계의 경우
- 일의 데이터가 1개
- 다의 데이터가 2개
그럼 조회 시에 row는 2개가 된다.
- JPA api 안에서 데이터 또한 객체가 2개 반환된다. 그걸 json으로 반환하면 원하는 값을 주는게 아니다.
- 우리는 일의 데이터 1개 안에 다의 데이터 2개를 원한다.
- 쿼리에 distinct를 붙여주면 자동으로 중복이 제거되면서 원하는 데이터를 얻을 수 있다.
- 하이버네이트v6 이후부터는 distinct없이도 중복이 제거된다
fetch join은 다대일에서도 똑같이 적용했는데 굳이 차이없는 것이 아닌가..?
일대다 fetch join시 장단점
- distinct로 인해 fetchjoin때문에 오는 중복 조회 되는 것을 막아준다.
- 큰 단점 : 페이징이 불가능 하다.
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o " +
"join fetch o.member m " +
"join fetch o.delivery d " +
"join fetch o.orderItems oi " +
"join fetch oi.item i ", Order.class)
.setFirstResult(1)
.setMaxResults(100)
.getResultList();
}
select distinct o1_0.order_id,d1_0.delivery_id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status,m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name,o1_0.order_date,oi1_0.order_id,oi1_0.order_item_id,oi1_0.count,i1_0.item_id,i1_0.dtype,i1_0.name,i1_0.price,i1_0.stock_quantity,i1_0.artist,i1_0.etc,i1_0.author,i1_0.isbn,i1_0.actor,i1_0.director,oi1_0.order_price,o1_0.status from orders o1_0 join member m1_0 on m1_0.member_id=o1_0.member_id join delivery d1_0 on d1_0.delivery_id=o1_0.delivery_id join order_item oi1_0 on o1_0.order_id=oi1_0.order_id join item i1_0 on i1_0.item_id=oi1_0.item_id
쿼리 안에 limit가 없다. 오잉? 근데 1번 데이터 부터 불러와 진다.
로그를 보면 warm 메세지가 있다.
2024-03-02T15:34:11.510+09:00 WARN 3969 --- [nio-8080-exec-2] org.hibernate.orm.query : HHH90003004:
firstResult/maxResults specified with collection fetch; applying in memory
- 페치조인을 쓰는데 메모리에서 페이징처리 해버릴거야(만약 데이터 10000개가 있으면 메모리가 out 되어버린다.)
한마디로 쿼리에서 페이징이 되는 것이 아니라 데이터를 모두 불러온 후에 애플리케이션 안에서 나눠버린다.
- 이 전에 데이터를 보면 4개 짜리 먼저 불러온 후에 jpa안에서 중복처리를 해주었다. 그러므로 데이터를 쿼리 안에서 limit 처리 할 수 없었던 것
- 일대다가 아니면 상관없다.
+ 컬렉션 패치 조인(일대다)는 1개만 사용할 수 있다. 컬렉션 둘 이상에 페치 조인을 사용하면 안된다.
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 향해플러스
- 스프링부트
- 백엔드 개발자 공부
- filter
- ArgumentResolver
- SpringBoot
- Java
- 컨트
- 향해플러스백엔드
- jpa api
- reject
- BindingResult
- HTTP
- 인터셉터
- Intercepter
- react실행
- thymleaf
- 항해플러스
- React
- rejectValue
- 향해99
- hypertexttransferprotocol
- 리터럴
- exception
- 스프링공부
- JPA
- 예외처리
- 백엔드 개발자 역량
- 항해99
- 로그인
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
글 보관함