티스토리 뷰
해당 설명의 소스코드는 링크를 걸어두었습니다.
JPA에서는 sql qeury를 한번 감싸놓은 것이기 때문에, 아무렇게나 쓰면 성능에 매우 안좋은 영향을 미칠 수 있다.
특히, select * from [DB name]과 같은 쿼리를 날리게 되면 join 관계가 많은 테이블은 성능이 매우 안좋아 질 수 있다.
그럼 가장 간단하지만 성능이 안좋은 방법으로 먼저 진행을 하면
@GetMapping("/api/v1/simple-orders") public List<Order> orderV1(){ List<Order> all = orderRepository.findAllByCriteria(new OrderSearch()); for (Order order: all){ order.getMember().getName(); // Lazy 강제 초기화 order.getMember().getAddress(); // Lazy 강제 초기화 } return all; }
Member.java
@Entity
@Table(name = "oreders")
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id @GeneratedValue
@Column(name = "oreder_id")
private Long id;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) // orderItem을 persist 하게되면 자동으로 persist 해준다.
private List<OrderItem> orderItems = new ArrayList<>();
@JsonIgnore
@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]
/**
* 생략
*/
}
다음과 같이 할 수 있다.
천천히 살펴보면 @JsonIgnore 어노테이션이 Order 클래스의 member 변수에 붙은걸 볼 수 있다.
그 이유로는 양방향으로 서로를 멤버 변수로 가지고 있기 때문에 서로 타고 들어가기 때문에 무한 루프에 빠지게 된다.
왜 무한루프에 빠지게 되냐면, JPA는 아무 설정없이 하나의 Entity가 다른 Entity를 멤버변수로 가지고 있다면,
그 멤버변수 또한 select query를 날리게 된다.
(이 문제는 밑에서 fetch join으로 더 자세하게 다뤄보도록 하자)
그리고 Lazy fetch의 경우에는 orders의 변수를 쓰일때에 DB에 접근을 하게 된다.
그렇다면 우선적으로는 임시적으로 ByteBuddyIntercepter를 넣어둔다.
Hibernate5model을 쓰면 해결 가능하나 중요하지 않으니 넘어가도록 하자.
또한, 위 소스코드처럼 Lazy 강제 초기화를 통해서 넣어줄 수 있지만, select query가 너무 많이 나가게 되는 문제가 있다.
하지만 저기서 가장 해결해야할 문제로는
Entity가 API를 직접 건드린다는 것이다. 이 것의 문제점은 앞선 장에서 다뤘으니 넘어가도록 하겠다.
우선 이 것부터 해결하자면,
@GetMapping("/api/v2/simple-order")
public List<SimpleOrderDto> orderV2(){
return orderRepository.findAllByCriteria(new OrderSearch()).stream()
.map(o -> new SimpleOrderDto(o))
.collect(Collectors.toList());
}
SimpleOrderDto.java
@Data
public class SimpleOrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address){
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
}
public SimpleOrderDto(Order order){
this.orderId = order.getId();
this.name = order.getMember().getName();
this.orderDate = order.getOrderDate();
this.orderStatus = order.getStatus();
this.address = order.getDelivery().getAddress();
}
}
이렇게 해결할 수 있다.
하지만 앞선 문제점인 Qeury를 너무 많이 날린다는 것은 변함이 없다.
이것을 바로 N+1문제라고 한다.
N+1문제와 fetch join
좀 더 자세하게 설명하면, 위 소스코드에서 order에 1번 쿼리를 날리면
member를 호출하는데 N번, delivery를 호출하는데 N번 이런식으로 주문 리스트를 조회하는데
여러개의 쿼리가 따라오게 되는 문제점을 바로 N+1문제라고 한다.
이 것을 해결하는 방법으로는 fetch join이 있는데 그냥 쉽게 생각하면
"SQL의 join 쿼리를 통해 한번에 가져오는 것" 이다.
사실 SQL을 조금만 해봤더라면 다들 inner join을 생각했을 것이다.
그것을 JPA에서는 다음과 같이 한다.
@GetMapping("/api/v3/simple-order")
public List<SimpleOrderDto> orderV3(){
return orderRepository.findAllWithMemberRepository().stream()
.map(o -> new SimpleOrderDto(o))
.collect(Collectors.toList());
}
public List<Order> findAllWithMemberRepository() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class).getResultList();
}
다음처럼 JPQL을 통해서 호출을 해주면 된다.
다음 방법은 member, delivery를 모두 불러서 필요한 변수를 DTO로 발라내는 과정이 있지만,
조금더 최적화 하는 방법으로는
public List<SimpleOrderDto> findOrderDtos() {
return em.createQuery(
"select new jpabook.jpashop.respoitory.OrderSimpleQeuryDto(o.id, m.name, o.orderDate, o.status, d.address) from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", SimpleOrderDto.class).getResultList();
}
다음과 같이 애초에 필요한 변수만 발라내어 쿼리를 날리는 방법도 있지만,
재사용성이 떨어지기 때문에 아주 많이 호출되는 api 같은 경우만 따로 파일을 분리해서 만드는 것이 방법이라고 할 수 있다.
'Back End > JPA' 카테고리의 다른 글
[JPA 기초] 1. 영속성 컨텍스트 (0) | 2022.10.03 |
---|---|
[API] 5. 1:N 관계 조회 최적화 - 1 (0) | 2022.09.17 |
[API] 3. Spring Data JPA API - 조회 (0) | 2022.08.30 |
[API] 2. Spring Data JPA API - 수정 (0) | 2022.08.30 |
[API] 1. Spring Data JPA API - 저장 (0) | 2022.08.30 |
- Total
- Today
- Yesterday
- OS
- NextJS
- spring
- API
- Data Engineering
- Front
- Linux
- feign client
- 리액트
- Container
- React
- 프론트엔드
- cs
- K8S
- broker
- KAFKA
- logback
- apache
- rhel
- zookeeper
- apache kafka
- Java
- Firebase
- docker
- consumer
- centos
- JPA
- Producer
- frontend
- spring boot
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |