public class Order {
@Id
@GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order",cascade = CascadeType.ALL)
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안에 member, address를 가지고 있는 엔티티이다.
//controller
@GetMapping("/api/v3/simple-orders")
public List<SimpleOrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithMemberDelivery();
List<SimpleOrderDto> result = orders.stream().map(o -> new SimpleOrderDto(o)).collect(Collectors.toList());
return result;
}
@Data
static class SimpleOrderDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
public SimpleOrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();//Lazy초기화, 추가 쿼리가 나가게됨
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();//Lazy초기화, 추가 쿼리가 나가게됨
}
}
--------------------------------------------------------------------------------------------
//repository
public List<Order> findAllWithMemberDelivery() {
return em.createQuery(
"select o from Order o"+
" join fetch o.member m "+
"join fetch o.delivery d ",Order.class).getResultList();
}
join fetch를 통해 Order안의 Member와 Adress를 한방 쿼리로 가져올 수 있다.
✔ sql하나로 Order객체, Member 객체, Address 객체를 가져온후에 Order안에 Member와 Address를 넣어서 반환해주고 그 Order를 OrderDto에 데이터를 집어 넣는 방식
하지만 우리는 Order와 Member, Adress의 모든 컬럼이 필요한 것이 아니다.
단지 DTO의 정보만 필요할뿐이다.
하지만 DTO를 만들기위해 모든 컬럼을 만들 수 밖에 없었다.
select
order0_.order_id as order_id1_6_0_,
member1_.member_id as member_i1_4_1_,
delivery2_.delivery_id as delivery1_2_2_,
order0_.delivery_id as delivery4_6_0_,
order0_.member_id as member_i5_6_0_,
order0_.order_date as order_da2_6_0_,
order0_.status as status3_6_0_,
member1_.street as street2_4_1_,
member1_.city as city3_4_1_,
member1_.zipcode as zipcode4_4_1_,
member1_.name as name5_4_1_,
delivery2_.street as street2_2_2_,
delivery2_.city as city3_2_2_,
delivery2_.zipcode as zipcode4_2_2_,
delivery2_.status as status5_2_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
✔ 이를 해결하기 위한 방법이 있다.
바로 createquery의 출력클래스를 DTO로 지정하는 것이다.(Order,Member,address 객체를 전부 가져와서 OrderDto에 주입하는게 아니라 OrderDto라는 조건을 가져오는것)
출력클래스를 DTO로 지정하고 가져오는 컬럼을 DTO의 필드값으로 지정한다면 필요없는 컬럼은 제외하고 DTO의 꼭 필요한 컬럼만 가져올 수 있다. (하지만 이러한 repository 코드는 범용적이지 않기 때문에 사용이 제한된다. 또한 repository는 한 컨트롤러에 국한되면 안된다. 따라서 범용repository코드에서 분리하고 새로운 repository에 만드는것이 좋다.)
@GetMapping("/api/v4/simple-orders")
public List<OrderSimpleQueryDto> ordersV4() {
return orderRepository.findOrderDtos();
}
public List<OrderSimpleQueryDto> findOrderDtos() {
return em.createQuery("select new jpabook.jpashop.repository.OrderSimpleQueryDto(o.id,m.name,o.orderDate,o.status,d.address) from Order o" +
" join o.member m " +
"join o.delivery d", OrderSimpleQueryDto.class).getResultList();
}
단 컬럼을 지정할때 주의할것은 객체를 넣으면 안된다는 것이다. OrderSimpleQueryDto(o)이렇게 넣어버리면 안된다.
왜냐하면 엔티티의 식별자로 넘어가기때문에 직접 값을 넣어줘야한다. address가 되는 이유는 엔티티가 아니라 값타입이기 때문이다.
select
order0_.order_id as col_0_0_,
member1_.name as col_1_0_,
order0_.order_date as col_2_0_,
order0_.status as col_3_0_,
delivery2_.street as col_4_0_,
delivery2_.city as col_4_1_,
delivery2_.zipcode as col_4_2_
from
orders order0_
inner join
member member1_
on order0_.member_id=member1_.member_id
inner join
delivery delivery2_
on order0_.delivery_id=delivery2_.delivery_id
확연히 줄어들은 sql을 볼 수 있다.
✔ 하지만 이 방법이 장점만 있는 것은 아니다. 이런식으로 원하는 컬럼만 지정한다면 다른 api에서의 활용이 극히 제한된다. 한 api에 특화된 repository메소드 이기 때문에 재활용이 거의 불가능하다.
✔ 요즘 워낙 네트워크 장비가 좋기때문에 필드가 엄청나게 큰게 아니라면 효과가 미비하다.
즉 2번까지 해보고 안됐을때 위와 같은 방법으로 하면 된다.
쿼리 방식 선택 권장 순서
1. 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
2. 필요하면 페치 조인으로 성능을 최적화 한다. 대부분의 성능 이슈가 해결된다.
3. 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다