본문 바로가기

JAVA/JPA & Querydsl

읽기 전용 Query, Transaction 성능 최적화

 영속성 컨텍스트는 엔티티 변경 감지를 위해  스냅샷 인스턴스를 메모리에 관리하므로 많은 메모리를 사용하게 됩니다.

한 트랜젝션 내에 엔티티 생성이나 수정이 없다면 읽기 전용으로 메모리를 최적화해 성능을 향상시킬 수 있습니다.

 

1. 스칼라 타입으로 조회

엔티티가 아닌 필드만 조회하면 영속성 컨텍스트는 조회 값을 관리하지 않습니다.

따라서 필드값 조회해서 DTO로 받는 방법을 사용할 수 있습니다.

 

select o.id, o.name, o.price from orderItem o

 

2. 읽기 전용 쿼리 힌트 사용

하이버네이트 전용 힌트 org.hibernate.readOnly 를 사용하면 읽기 전용으로 조회할 수 있다.

 

TypedQuery<OrderItem> query = em.createQuery("select o from Order o", Order.class);
query.setHint("org.hibernate.readOnly",true);

 

3. 읽기 전용 트랜잭션 사용 @Transactional(readOnly = true)

스프링 프레임워크의 @Transactional(readOnly = true) 어노테이션으로 트랜잭션을 읽기 전용으로 설정 합니다.

readOnly=true 옵션은 하이버네이트 세션 플러시 모드를 MANUAL 로 설정 합니다. 이 설정은 강제로 플러시를 호출해야만 플러시가 일어납니다. 영속성 컨텍스트 스냅샷 비교 로직이 수행되지 않으니 성능이 향상합니다.

 

하이버네이트 세션은 JPA 엔티티 매니저를 하이버네이트로 구현한 구현체로, 엔티티 매니저의 unwrap() 메소드를 호출하면 하이버네이트 세션을 구할 수 있습니다.
Session session = entityManager.unwrap(Session.class);

 

실제로 이것은 1번 방법과 함께 제일 많이 사용되는 성능 최적화 방법입니다.

 

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderItemService {

    private final MovieRepository movieRepository;

    public Book getOne(final Long id) {
        return bookRepository.findById(id).orElseThrow(EntityNotFoundException::new);
    }

    @Transactional
    public Book saveBook(final BookInsertRequest bookInsertRequest) {
        Book book = Book.of(bookInsertRequest.getName(), bookInsertRequest.getStockQuantity(), bookInsertRequest.getPrice(),
                bookInsertRequest.getAuthor(), bookInsertRequest.getIsbn());
        return bookRepository.save(book);
    }
}

 

 서비스 클래스에 읽기전용 트랜잭션 어노테이션을 달아주고, 엔티티 변경이 필요한 메소드에만 @Transactional 어노테이션을 달아 변경이 가능하도록 해줍니다.

 

4. 트랜잭션 밖에서 읽기

트랜잭션 밖에서 조회하는 것으로 오직 조회가 목적일때 사용할 수 있습니다.

 

@Transactional(propagation = Propagarion.NOT_SUPPORTED)

트랜잭션을 커밋하거나 쿼리 실행시 작동하는 플러시가 트랜잭션이 존재하지 않으므로 작동할 일이 없게 됩니다.

 

 

결론

메모리 사용 최적화
- 스칼라 타입으로 조회하거나 하이버네이트 읽기전용 쿼리 힌트 사용하여 조회.
플러시 호출을 막아서 속도를 최적화
- 읽기전용 트랜잭션을 사용하거나, 트랜잭션을 사용하지않고 조회.

 스프링 프레임워크를 사용한다면 @Transactional(readOnly = true) 어노테이션으로 트랜잭션을 읽기 전용으로 설정하고 DTO로 필요한 필드만 조회해 사용하는 것으로 편리하게 최적화가 가능합니다.