엔티티가 영속성 컨텍스트에 관리되면 1차 캐시부터 변경 감지까지 얻을 수 있는 혜택이 많다. 하지만 영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관하므로 더 많은 메모리르 사용하는 단점이 있다.
예를 들어 100건의 구매 내용을 출력하는 단순한 조회 화면이 있다고 가정해보자. 그리고 조회한 엔티티를 다시 조회할 일도 없고 수정할 일도 없이 딱 한 번만 읽어서 화면에 출력하면 된다.
이때는 읽기 전용으로 엔티티를 조회하면 메모리 사용량을 최적화할 수 있다.
스칼라 타입으로 조회
가장 확실한 방법은 다음처럼 엠티티가 아닌 스칼라 타입으로 모든 필드를 조회하는 것이다.
스칼라 타입은 영속성 컨텍스트가 결과를 관리하지 않는다.
select o.id, o.name, o.price from Order o
읽기 전용 쿼리 힌트 사용
하이버네이트 전용 힌트인 org.hibernate.readOnly를 사용하면 엔티티를 읽기 전용으로 조회할 수 있다.
읽기 전용이므로 영속성 컨텍스트는 스냅샷을 보관하지 않는다. 따라서 메모리 사용량을 최적화할 수 있다.
단 스냅샷이 없으므로 엔티티를 수정해도 데이터베이스에 반영되지 않는다.
TypedQuery<Order> query = em.createQuery("select o from Order o", Order.class);
query.setHint("org.hibernate.readOnly", true);
읽기 전용 트랜잭션 사용
스프링 프레임워크를 사용하면 트랜잭션을 읽기 전용 모드로 설정할 수 있다.
@Transactional(readOnly = true)
public MemberDTO getMember(long memberId) {
...
}
readOnly = true 속성을 주면 스프링 프레임워크가 하이버네이트 세션 플러시 모드를 MANUAL로 설정한다.
이렇게 하면 강제로 플러시를 호출하지 않는 한 플러시가 일어나지 않는다. 따라서 트랜잭션을 커밋해도 영속성 컨텍스트를 플러시 하지 않는다. 플러시 하지 않으니 당연히 등록, 수정, 삭제는 동작하지 않는다. 하지만 플러시 할 때 일어나는 스냅샷 비교와 같은 무거운 로직들을 수행하지 않으므로 성능이 향상된다.
물론 트랜잭션을 시작했으므로 트랜잭션 시작, 로직 수행, 트랜잭션 커밋의 과정은 이루어지고, 단지 영속성 컨텍스트를 플러시 하지 않을 뿐이다.
참고
엔티티 매니저의 플러시 설정에는 AUTO, COMMIT 모드만 있고, MANUAL 모드가 없다. 반면 하이버네이트 세선(org.hibernate.Session)의 플러시 설정에는 MANUAL 모드가 있다.
MANUAL 모드는 강제로 플러시를 호출하지 않으면 절대 플러시가 발생하지 않는다.
하이버네이트 세션은 엔티티 매니저의 unwrap() 메서드를 호출하면 구할 수 있다.
Session session = entityManager.unwrap(Session.class);
트랜잭션 밖에서 읽기
JPA에서 데이터를 변경하려면 트랜잭션은 필수다. 따라서 조회가 목적일 때만 사용해야 한다.
@Transactional(propagation = Propagation.NOT_SUPPORTED) // Spring
public MemberDTO getMember(long memberId) {
...
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) // J2EE 표준 컨테이너 사용 시
public MemberDTO getMember(long memberId) {
...
}
트랜잭션을 사용하지 않으면 플러시가 일어나지 않으므로 조회 성능이 향상된다.
정리하자면 읽기 전용 데이터를 조회할 때, 메모리를 최적화하려면 스칼라 타입으로 조회하거나 하이버네이트가 제공하는 읽기 전용 쿼리를 힌트를 사용하면 되고,
플러시 호출을 막아서 속도를 최적화하려면 읽기 전용 트랜잭션을 사용하거나 트랜잭션 밖에서 읽기를 사용하면 된다. 참고로 스프링 프레임워크를 사용하면 읽기 전용 트랜잭션을 사용하는 것이 편리하다.
@Transactional(readOnly = true) // 읽기 전용 트랜잭션 - 플러시가 작동하지 않아 성능 절약
public List<Member> getMemberList(long memberId) {
return em.createQuery("select m from Member m", Member.class)
.setHint("org.hibernate.readOnly", true) // 읽기 전용 쿼리 힌트 - 메모리 절약
.getResultList();
}
'JPA' 카테고리의 다른 글
트랜잭션을 지원하는 쓰기 지연 (0) | 2022.01.25 |
---|---|
JPA 배치 처리 (0) | 2022.01.25 |
영속성 컨텍스트에 따른 엔티티 비교 (0) | 2022.01.24 |
JPA 예외 처리 (0) | 2022.01.24 |
엔티티 그래프로 연관관계 조회 하기 (0) | 2022.01.21 |