*인프런 '실전! 스프링부트와 JPA 활용2' 강의 내용에서 배운 내용을 정리한 것입니다*

 

OSIV: Open Session In View, 하이버네이트

OEIV: Open EntityManager In View, JPA(관례상 OSIV라 한다)

-설정

spring.jpa.open-in-view: true

 

-OSIV와 지연로딩 

OSIV 전략은 최초 데이터베이스 시작 시점부터 API 응답이 끝날 때까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지한다.

지연로딩은 영속성 컨텍스트가 살아있어야 가능하고, 영속성 컨텍스트는 데이터베이스 커넥션을 유지한다. 

 

-문제점

OSIV 전략은 너무 오랜시간동안 데이터베이스 커넥션 리소스를 사용하기 때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있고,  이것은 곧 장애로 이어진다 

 

-결론 

따라서 OSIV를 끄고, 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고 데이터베이스 커넥션을 반환해야지 커넥션 리소스를 낭비하지 않는다. 

이렇게 되면, 지연로딩을 트랜잭션 안에서 처리해야 한다. 이것은 많은 지연 로딩 코드를 트랜잭션 안으로 넣어야하는 단점이 있다. 

트랜잭션이 끝나기 전에 지연로딩을 강제로 호출해 두어야 한다

이것을 하기 위해서는 Command와 Query를 분리하는 방법을 사용해야한다. 

 

예를 들면 OrderService 에서

OrderServie: 핵심 비즈니스 로직

OrderQueryService: 화면이나 API에 맞춘 서비스(주로 읽기 전용 트랜잭션 사용)

 

두 서비스 모두 트랜잭션을 유지하면서 지연 로딩을 사용할 수 있다 

 

 

*인프런 '실전! 스프링부트와 JPA 활용2' 강의 내용에서 배운 내용을 정리한 것입니다*

 

-대부분의 페이징+컬렉션 엔티티 조회문제는 다음과 같은 방법으로 해결할 수 있다 

1. ToOne관계는 모두 페치조인한다. ToOne관계는 row수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않는다

 

2. 컬렉션은 지연로딩으로 조회한다 

 

3. 지연로딩 성능 최적화를 위해 hibernate.default_bath_fetch_size, @BatchSize를 적용한다.

-hibernate.defqult_bath_fetch_size: 글로벌 설정(applications.yml에서 설정)

-@BatchSize: 개별 최적화 (컬렉션은 컬렉션 필드에, 엔티티는 엔티티 클래스에 적용한다)

이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 sizea만큼 IN 쿼리로 조회한다 

 

spring: jpa:
        properties:
          hibernate:
            default_batch_fetch_size: 1000

-장점

쿼리 호출 수가 1+N에서 1+1로 최적화 된다

조인보다 DB데이터 전송량이 최적화된다

DB데이터 전송량이 감소한다

컬렉션 페치 조인은 페이징이 불가능하지만, 이 방법은 페이징이 가능하다

-결론

ToOne관계는 페치 조인을 해도 페이징에 영향을 주지 않기 때문에 그냥 페치조인을 사용해서 쿼리 수를 줄이고, 나머지는 hibernate.default_batch_fetch_size로 최적화 하자

*인프런 '실전! 스프링부트와 JPA 활용2' 강의 내용에서 배운 내용을 정리한 것입니다*

 

이전 내용과 같은 방식으로 엔티티를 직접 노출하지 않고 DTO로 반환하고, 지연로딩을 사용하며, 1+N문제를 해결하기 위해 페치조인을 사용해서 컬렉션을 조회한다 

/OrderApiController 

@GetMapping("/api/v3/orders")
public List<OrderDto> ordersV3() {
    List<Order> orders = orderRepository.findAllWithItem();
    List<OrderDto> result = orders.stream()
            .map(o -> new OrderDto(o))
            .collect(toList());
    return result;
}

/OrderRepository

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();
}

컬렉션을 조회하는 경우는 일대다(@OneToMany),다대다(ManyToMany)이므로 조회하게되면 데이터베이스 row가 증가한다. 하나의 order에 여러개의 orderItems가 있는 경우, orderItems에 맞춰서 여러개의 order row가 생길 수 있다.     

이때, distinct를 사용하면 중복을 걸러서 조회할 수 있다

 

-JPQL에서 distinct 기능 2가지 

1. 디비에 distinct 키워드 날려주고 

2. 엔티티가 중복인 경우에 중복을 걸러서 컬렉션에 담아준다 

 

하지만 distinct를 사용하면 페이징이 불가능하다 

 

참고: 컬렉션 페치 조인을 사용하면 페이징이 불가능하다.

두개 이상의 컬렉션에 페치 조인을 사용하면 데이터가 부정합하게 조회될 수 있어 안된다 

(OrderItem과 Item은 다대일 관계이므로, orderitem을 조회할 때 Item도 같이 조회 된다)

 

 

*인프런 '실전! 스프링부트와 JPA 활용2' 강의 내용에서 배운 내용을 정리한 것입니다*

 

참고: dto는 엔티티를 참조해도 괜찮다

 

1. 엔티티를 DTO로 변환하는 방법을 선택한다

2. 필요하면 페치조인으로 성능을 최적화한다 (대부분의 성능 이슈가 해결된다)

 

예시)

/OrderRepository

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();

 

==> 내가 원하는 것만 select 하고, 외부의 모습은 건드리지 않은 상태로 내부에서 원하는 것으로만 성능을 튜닝할 수 있다.

엔티티를 조회했기 때문에 비즈니스 로직을 써서 데이터 변경을 할 수 있다.

재사용성이 있다

 

3. 그래도 안되면 JPA에서 직접 DTO를 조회하는 방법을 사용한다 

 

예시)

/OrderSimpleQueryRepository

select 프로젝션에 new와 파일 경로를 설정한 다음 필요한 정보들을 다 입력한다

@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {
  
      private final EntityManager em;
      
      public List<OrderSimpleQueryDto> findOrderDtos() {
          return em.createQuery(
                  "select new
  jpabook.jpashop.repository.order.simplequery.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();

   } 
 }

===> 한번에 내가 원하는 데이터를 JPQL을 짜서 가져온다. 해당 DTO에만 사용 가능하기 때문에 딱딱하다

데이터 변경 불가능하고, API 스펙이 리포지토리에 들어가 있기 때문에 논리적 계층이 깨진다(repository는 엔티티 조회 용도로 사용해야한다)

 

4. 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다 

+ Recent posts