인프런 {실전! 스프링 부트와 JPA 활용2} 강의를 완강 후 기억해야 할 내용을 3가지 뽑았다.
@Data
@AllArgsConstructor
static class Result<T> { // json 스펙을 맞추기 위한 감싸기 용도
private int count;
private T data;
}
Rest api 개발 시 json 스펙을 맞추어야 한다.
예를 들어 여러 데이터가 있는데 데이터형이 배열일 수도 있고 일반 자료형일 수도 있다. 이 데이터들을 그대로 json방식으로 변환하면 오류가 발생한다. 그러니 보내려는 주 내용을 제네릭을 사용한 data에 담아 보내도록 하자. 그러면 데이터 자료형이 다르더라도 문제없다.
@Data
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();
}
}
dto란?: Entity를 그대로 내보낸다면 Entity변경시 api spec또한 변경이 된다. 그러면 안되므로 client에서 필요한 데이터들만 담아서 주도록 하자. 필요한 데이터들이 멤버변수로 들어있는 클래스가 dto이다. 물론 request를 받을 때에도 dto사용이 가능하다.
N+1에 자세한 내용이 담긴 글: https://incheol-jung.gitbook.io/docs/q-and-a/spring/n+1
Entity에 Collection 변수가 들어있을 경우 발생하는 문제들이 많다. toMany 로 엮인 관계들 같은 경우에 query가 Collection의 length만큼 나간다던지와 같은 문제들이다.
해결책: fetch join과 default_batch_fetch_size를 활용하자.
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o" + // distinct가 있으면 Order 중에 id값 중복이 있으면 중복을 제거한다.
" 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과 distinct를 사용하여 N+1문제를 해결 할 수 있다. 하지만 단순한 fetch join으로는 페이징을 사용하지 못하는 단점이 있다. 그래서 default_batch_fetch_size 를 활용하자. default_batch_fetch_size를 yml에 작성하고
public List<Order> findAllWithMemberDelivery(int offset, int limit) {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d", Order.class)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
다음과 같이 @ToOne인 변수들만 fetch join을 하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size 만큼 IN 쿼리로 조회한다. 이 방법은 보이는 것 처럼 페이징도 가능하다.
osiv란 요청이 시작되는 순간부터 끝날 때 까지 영속성 컨텍스트가 살아있는 상태를 말한다. 이는 트래픽이 많은 api server에서 문제를 일으킬 수 있다. 예를 들어 api 요청 로직에 외부 api 호출이 있는데 외부 api 호출이 만약 3분이 걸린다면 3분동안 영속성 컨텍스트가 살아있고 이는 그 시간동안 db connect pool을 잡고 있는 것이다. osiv는 default 값이 true이기 때문에 application.yml에 open-in-view: false를 써주어야 한다.
그래서 OSIV를 사용하지마?: 고객 서비스를 하는 트래픽이 많은 api server는 OSIV를 끄고, ADMIN 처럼 커넥션을 많이 사용하지 않는 곳에서는 OSIV를 켜도록 하자.