JPA

15장 고급 주제와 성능 최적화

iksadnorth 2023. 9. 9. 11:22

해당 게시물은 책 '자바 ORM 표준 프로그래밍'을 읽고 작성했습니다.

👣 개요

해당 게시글은 JPA에서의 예외 처리, 엔티티 비교 시 주의사항, 성능 최적화에 대한 주제로 
작성되었다.

 

👣 예외 처리

순수 JPA 단계에서의 예외

1. 트랜잭션 롤백을 발생시키는 예외
해당 예외는 심각한 예외이므로 강제 롤백된다.

2. 트랜잭션 롤백을 발생시키지 않는 예외
그다지 심각하지 않는 예외이므로 개발자 선택에 의해 커밋하던지 롤백한다.
예를 들어, 단일 조회를 했는데 조회된 내용이 하나도 없으면 발생하는 예외 같은 것을 의미한다.

 

Spring Framework에서의 JPA 예외 변환

아래 Effective Java의 내용에 따르면 저수준의 라이브러리[Hibernate]에서 발생한 예외를 
고수준의 라이브러리[Spring Data JPA]에서 그대로 노출되면 오류의 원인을 파악하기 어려워진다.
때문에 저수준의 예외가 발생 시, 고수준 라이브러리에 맞춰서 에러 메시지를 바꾸고
해당 라이브러리에서 정의한 새로운 예외로 바꿔 호출한다.

try {
    ... // 저수준 추상화를 사용
} catch (LowerLevelException cause) {
    throw new HigherLevelException(cause);
}
 

Effective Java 제 10장 - 예외

예외를 흐름제어와 같은 예외가 아닌 상황에서 사용하지 말라! 아래와 같이 흐름제어를 위해 if문을 대체하는 코드를 사용하지 않아야 한다. 성능적으로 손해를 많이 볼 수 있고 무엇보다도 가

ikadnorth.tistory.com

마찬가지로 Spring 프레임 워크는 Spring 에 맞춘 수준의 예외로 다시 예외를 던지게 된다.
이것을 가능하게 하려면 다음과 같은 Bean을 등록하면 된다.

@Bean
public PersistenceExceptionTranslationPostPocessor exception Translation () {
    return new PersistenceExceptionTranslationPostProcessor();
}

 

트랜잭션 Rollback 시, 주의 사항

트랜잭션의 롤백은 DB의 원자성을 지키는 것이지 Java 엔티티 객체의 원자성을 고려하지는 않는다.
때문에 잘못 수정된 채로 영속성 컨텍스트에 남게 되면 해당 왜곡된 정보가 그대로 DB에 반영되버린다.
이를 막기 위해 꼭 EntityManager.clear() 메서드를 호출해서 잘못된 엔티티 정보를 삭제해야 한다.

Spring Framework에서는 해당 방법을 이미 기본적으로 실행하기 때문에 Rollback 발생 시,
EntityManager.clear() 가 발생한다는 점을 꼭 인지해야 한다.

 

👣 엔티티 비교

엔티티는 같은 영속성 컨텍스트 내부에서는 1차 캐시에 의해
무조건 같은 id의 엔티티는 같은 객체 주소를 가짐을 보장된다.

하지만 다른 영속성 컨텍스트 내부의 엔티티들은 이를 지키지 못한다.

이런 고려 사항은 Test에서 세심히 이뤄져야 한다.
아래는 Test 코드에 @Transactional이 적용되어서 test까지 영속성 컨텍스트가 
동일한 사례다.

아래는 Test 코드에 @Transactional이 적용되지 못해 영속성 컨텍스트가 다른 경우다.

때문에 ==를 이용해서 엔티티가 동일함을 따질 때는 이런 점에 유의해서 
코드를 작성해야 한다.

 

👣 성능 최적화 - N+1 문제

JPA에서 지연 로딩 상태로 요소를 개별적으로 하나씩 로드하면
1개의 쿼리로 해결될 일이 1+N개의 쿼리로 해결되는 참사가 발생한다.

시나리오 1)
글로벌 Fetch 전략을 FetchType.EAGER 설정 후,
em.find()로 조회

-> 해당 경우 애초의 쿼리가 Join으로 나가기 때문에 
N+1 문제가 발생하지 않음

시나리오 2)
글로벌 Fetch 전략을 FetchType.EAGER 설정 후,
JPQL[SELECT m FROM MEMBER]로 조회

-> Member 엔티티 조회하자 마자 N+1 문제가 발생

시나리오 3)
글로벌 Fetch 전략을 FetchType.LAZY 설정 후,
JPQL[SELECT m FROM MEMBER]로 조회

-> Member 엔티티 조회하면 오직 Member만 조회함.
하지만 차후에 비즈니스 로직에서 컬렉션 순회를 하게 되면 
N개의 쿼리를 보내서 과도한 트래픽을 감당해야 한다.

시나리오 4)
글로벌 Fetch 전략을 FetchType.LAZY 설정 후,
JPQL[SELECT m FROM MEMBER JOIN FETCH m.orders]로 조회

-> 해당 경우 애초의 쿼리가 Join으로 나가기 때문에 
N+1 문제가 발생하지 않음.

시나리오 2-1)
글로벌 Fetch 전략을 FetchType.EAGER 설정 후,
일대다 연관관계의 필드 위에 @BatchSize(size = 5)를 설정하고
JPQL[SELECT m FROM MEMBER]로 조회

-> Member 엔티티 조회하자 마자
1개의 Member 엔티티 조회 쿼리
+ 5개씩 Order 엔티티 조회 쿼리
+ 5개씩 Order 엔티티 조회 쿼리
+ ...

시나리오 2-2)
글로벌 Fetch 전략을 FetchType.EAGER 설정 후,
일대다 연관관계의 필드 위에 @Fetch(FetchMode.SUBSELECT)를 설정하고
JPQL[SELECT m FROM MEMBER]로 조회

-> Member 엔티티 조회하자 마자
1개의 Member 엔티티 조회 쿼리
+ 1개의 Order 엔티티 일괄 조회 쿼리

 

👣 성능 최적화 - 읽기 전용 쿼리

영속성 컨텍스트 내부의 1차 캐시는 변경 감지를 위해 스냅샷 인스턴스를 추가로 보관한다.
하지만 수정, 생성, 삭제의 상황이 아니라면, 즉 조회만 할 것이라면
굳이 스냅샷 메모리를 사용할 필요는 없다.
이것을 위한 총 4가지의 방법이 존재한다.

1. 스칼라 타입으로 조회
2. 읽기 전용 쿼리 힌트 사용
3. 읽기 전용 트랜잭션 사용
4. 트랜잭션 밖에서 읽기

스칼라 타입으로 조회
엔티티로 조회하는 것이 아닌 프로젝션을 이용해서 튜플로서 받는다면 
영속성 컨텍스트는 추적하지 않는다.

읽기 전용 쿼리 힌트 사용
하이버네이트 전용 힌트인 'org.hibernate.readOnly'를 사용하면
엔티티를 읽기 전용으로 조회가능하다.

읽기 전용 트랜잭션 사용
Spring Framework에선 트랜잭션을 일기 전용 모드로 설정할 수 있다.
해당 설정은 플러시를 수동 모드로 바꿔서 커밋을 해도 플러시는 일어나지 않는다.

트랜잭션 밖에서 읽기
트랜잭션 밖에서 엔티티를 조회하면 플러시를 호출할 이유가 없기 때문에
성능 향상에 도움된다.

 

👣 성능 최적화 - 배치 처리

소규모의 App이라면 걱정없지만 대규모의 데이터를 처리하는 JPA는 
1차 캐시를 위한 메모리가 매우 요구된다. 혹시나 너무 많은 엔티티가 쌓이게 되면
Out Of Memory가 발생하면서 서버가 다운될 수도 있다.

때문에 너무 많은 데이터를 등록 및 수정하는 쿼리는 의도적으로 
적당한 주기로 flush와 clear를 해줘야 한다.

등록 배치 처리
수정 배치 처리

 

👣 성능 최적화 - 쓰기 지연

영속성 컨텍스트에는 쓰기 지연 SQL 저장소가 있어서 
한 번에 쓰기 쿼리를 내보내는 전략을 사용한다.

보통의 쓰기 쿼리

하지만 만약 같은 엔티티를 쓰는 쿼리 사이에 뜬금없이 다른 엔티티를 쓰는 쿼리가 삽입되면
Insert문을 도중에 잘라먹어야 해서 쓰기 성능이 떨어질 수도 있다.

순서 때문에 2개면 충분한 삽입 쿼리가 3개로 늘어남.

뿐만 아니라 Id값을 결정할 때, GenerationType.IDENTITY를 사용하면 ID 결정을 DB가 하기 때문에 
쓰기 지연을 할 수가 없다. 때문에 배치 쿼리 호출이 아예 불가능하다.

이것에 유의해 ID 생성 전략을 짜는 것이 좋다.

약간의 오버헤드가 있지만 GenerationType.TABLE를 사용하면 쓰기 지연이 가능하다.

'JPA' 카테고리의 다른 글

16장 트랜잭션과 락, 2차 캐시  (0) 2023.09.12
14장 컬렉션과 부가 기능  (0) 2023.09.08
13장 웹 애플리케이션과 영속성 관리  (0) 2023.09.07
12장 Spring Data JPA  (0) 2023.09.06
영속성 컨텍스트 vs JPQL  (0) 2023.09.06