Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 내일배움카드
- 디자인강의
- 오픈패스
- 환급챌린지
- 자바
- 티스토리챌린지
- 국비지원교육
- mysql
- 패스트캠퍼스
- UXUIPrimary
- 백엔드개발자
- 백준
- 국비지원취업
- 국비지원
- 오픈챌린지
- UXUI기초정복
- Java
- 오블완
- 디자인챌린지
- Be
- 부트캠프
- baekjoon
- 객체지향
- JPA
- KDT
- Spring
- 디자인교육
- UXUI챌린지
- OPENPATH
- 백엔드 부트캠프
Archives
- Today
- Total
군만두의 IT 개발 일지
[스터디10] 14. 고급 주제와 성능 최적화 본문
목차
15장. 고급 주제와 성능 최적화
15.1 예외 처리
15.1.1 JPA 표준 예외 정리
- JPA 표준 예외들은 javax.persistence.PersistenceException의 자식 클래스이며, 모두 RuntimeException의 자식이므로 언체크 예외다.
- JPA 표준 예외는 크게 2가지로 나눌 수 있다.
- 트랜잭션 롤백을 표시하는 예외: 심각한 예외이므로 복구해선 안 된다.
- 트랜잭션 롤백을 표시하지 않는 예외: 개발자가 트랜잭션을 커밋할지 롤백할지 판단한다.
- 주요 예외
- EntityExistException: EntityManager.persist() 호출 시 이미 같은 엔티티가 있으면 발생한다.
- EntityNotFoundException: EntityManager.getReference()를 호출했는데 실제 사용 시 엔티티가 존재하지 않으면 발생한다. (refresh, lock에서도 발생)
- TransactionRequiredException: 트랜잭션이 필요할 때 트랜잭션이 없으면 발생한다.
- NonUniqueResultException: Query.getSingleResult() 호출 시 결과가 둘 이상일 때 발생한다.
15.1.2 스프링 프레임워크의 JPA 예외 변환
- 서비스 계층에서 데이터 접근 계층의 구현 기술에 직접 의존하는 것은 좋은 설계가 아니다.
- 스프링 프레임워크는 데이터 접근 계층에 대한 예외를 추상화해서 개발자에게 제공한다.
15.1.3 스프링 프레임워크에 JPA 예외 변환기 적용
- JPA 예외를 스프링 예외로 변경하려면 PersistenceExceptionTranslationPostProcessor를 스프링 빈으로 등록한다.
15.1.4 트랜잭션 롤백 시 주의사항
- 트랜잭션을 롤백하는 것은 데이터베이스의 반영사항만 롤백하는 것이지 수정한 자바 객체까지 원상태로 복구해주지는 않는다.
- 엔티티를 조회해서 수정하는 중에 문제가 발생해서 롤백하면 데이터베이스의 데이터는 원래대로 복구되지만 객체는 수정된 상태로 영속성 컨텍스트에 남아 있다. 따라서 새로운 영속성 컨텍스트를 생성하거나 EntityManager.clear()를 호출해서 영속성 컨텍스트를 초기화한 다음에 사용해야 한다.
- 기본 전략인 트랜잭션당 영속성 컨텍스트 전략은 문제가 발생하면 트랜잭션 AOP 종료 시점에 트랜잭션을 롤백하면서 영속성 컨텍스트도 함께 종료하므로 문제가 발생하지 않는다.
15.2 엔티티 비교
15.2.1 영속성 컨텍스트가 같을 때 엔티티 비교
- 영속성 컨텍스트가 같으면 엔티티를 비교할 때 다음 3가지가 모두 같다.
- 동일성(==) 비교
- 동등성(equals()) 비교
- 데이터베이스 동등성(@Id) 비교
15.2.2 영속성 컨텍스트가 다를 때 엔티티 비교
- 영속성 컨텍스트가 다르면 동일성(==) 비교에 실패한다.
- 따라서 엔티티의 비교에 equals()와 hashCode()를 사용한 동등성 비교를 한다.
- 엔티티를 비교할 때는 비즈니스 키를 활용한 동등성 비교를 권장한다.
15.3 프록시 심화 주제
15.3.1 영속성 컨텍스트와 프록시
- 영속성 컨텍스트는 자신이 관리하는 영속 엔티티의 동일성을 보장한다.
- 영속성 컨텍스트에 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다. 반대로 프록시로 먼저 조회한 후 엔티티를 조회하면 영속성 컨텍스트는 프록시를 반환한다.
15.3.2 프록시 타입 비교
- 프록시는 원본 엔티티를 상속받아서 만들어지므로 프록시로 조회한 엔티티의 타입을 비교할 때는 == 비교가 아닌 instanceof를 사용해야 한다.
15.3.3 프록시 동등성 비교
- 엔티티의 동등성을 비교하려면 비즈니스 키를 사용해서 equals() 메소드를 오버라이딩하고 비교한다.
- equals() 메소드를 구현할 때는 비교 대상이 프록시일 수도 있다는 점을 고려해야 한다.
- 프록시의 타입 비교는 == 비교 대신에 instanceof를 사용해야 한다.
- 프록시의 멤버변수에 직접 접근하면 안 되고 대신에 접근자 메소드를 사용해야 한다.
15.3.4 상속관계와 프록시
- 프록시를 부모 타입으로 조회하면 부모의 타입을 기반으로 프록시가 생성되는 문제가 발생한다.
- JPQL로 대상을 직접 조회하거나 프록시 벗기기, 방문자 패턴(Visitor Pattern) 등으로 해결할 수 있다.
15.4 성능 최적화
15.4.1 N+1 문제
N+1 문제는 JPA를 사용할 때 가장 주의해야 할 성능 문제다.
- N+1 문제: 최초 쿼리 1개를 실행했을 때, 연관된 엔티티를 조회하기 위해 추가로 N개의 쿼리가 발생하는 문제
- 즉시 로딩에서 N+1 문제:
- JPQL을 사용하면 JPA는 글로벌 페치 전략을 무시하고 JPQL만 사용해서 SQL을 생성한다.
- 즉시 로딩으로 설정되어 있으면 연관된 엔티티를 각각 조회하기 위해 추가 쿼리가 N번 발생한다.
- 지연 로딩에서 N+1 문제:
- 지연 로딩으로 설정하면 조회 시점에는 N+1 문제가 발생하지 않는다.
- 하지만 연관된 엔티티를 실제로 사용하는 시점에 지연 로딩이 발생하여 결국 N+1 문제가 발생한다.
- N+1 문제 해결 방법
- 페치 조인(Fetch Join) 사용: SQL 조인을 사용해서 연관된 엔티티를 함께 조회하므로 N+1 문제가 발생하지 않는다.
- 페치 조인 대상에는 별칭을 줄 수 없고, 둘 이상의 컬렉션을 페치 조인할 수 없으며, 컬렉션 페치 조인 시 페이징 API를 사용할 수 없다.
- @BatchSize 사용: hibernate.default_batch_fetch_size 옵션으로 글로벌 설정하거나 @BatchSize로 개별 설정한다. 지연 로딩 시 설정한 size만큼 WHERE IN 쿼리로 묶어서 조회한다.
- 1+N 문제를 1+1 문제로 최적화할 수 있다.
- @Fetch 사용: @Fetch에 FetchMode를 SUBSELECT로 사용하면 연관된 데이터를 조회할 때 서브 쿼리를 이용해서 N+1 문제를 해결한다.
- 페치 조인(Fetch Join) 사용: SQL 조인을 사용해서 연관된 엔티티를 함께 조회하므로 N+1 문제가 발생하지 않는다.
15.4.2 읽기 전용 쿼리의 성능 최적화
- 엔티티가 영속성 컨텍스트에 관리되면 1차 캐시부터 변경 감지까지 얻을 수 있는 혜택이 많다.
- 하지만 영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관하므로 더 많은 메모리를 사용한다.
- 조회한 엔티티를 다시 조회하거나 수정할 일이 없다면 읽기 전용으로 엔티티를 조회하여 메모리 사용량을 최적화할 수 있다.
- 읽기 전용 쿼리 최적화 방법:
- 스칼라 타입으로 조회: 엔티티가 아닌 필드만 조회하면 영속성 컨텍스트가 결과를 관리하지 않는다.
- 읽기 전용 트랜잭션 사용: @Transactional(readOnly = true)를 사용하면 스냅샷을 보관하지 않는다.
- 트랜잭션 밖에서 읽기: query.setHint("org.hibernate.readOnly", true)를 사용한다.
15.4.3 배치 처리
- 수천에서 수만 건 이상의 엔티티를 한 번에 등록, 수정, 삭제할 때 영속성 컨텍스트에 엔티티가 계속 쌓이면 OutOfMemory가 발생할 수 있다.
- 따라서 일정 단위마다 영속성 컨텍스트의 엔티티를 데이터베이스에 플러시하고 초기화해야 한다.
JPA 페이징 배치 처리
- JpaPagingItemReader를 사용하여 페이지 단위로 데이터를 읽어 처리한다.
- 페이지 단위로 영속성 컨텍스트를 플러시하고 초기화하여 메모리 문제를 방지한다.
- 다만 offset 방식의 페이징은 데이터가 많을수록 성능이 저하될 수 있다.
하이버네이트 scroll 사용
- 하이버네이트 Session의 scroll 기능을 사용하면 데이터베이스 커서를 사용하여 메모리 부담을 줄일 수 있다.
- 대량의 데이터를 효율적으로 처리할 수 있다.
하이버네이트 무상태 세션 사용
- 영속성 컨텍스트를 만들지 않고 2차 캐시도 사용하지 않는다.
- 엔티티를 수정하려면 무상태 세션이 제공하는 update() 메소드를 직접 호출해야 한다.
15.4.4 SQL 쿼리 힌트 사용
- JPA는 데이터베이스 SQL 힌트 기능을 제공하지 않는다.
- 하이버네이트를 사용하면 SQL 힌트를 사용할 수 있다.
- query.setHint("org.hibernate.comment", "힌트 내용")을 사용한다.
15.4.5 트랜잭션을 지원하는 쓰기 지연과 성능 최적화
- 트랜잭션을 지원하는 쓰기 지연의 장점은 데이터베이스 테이블 row에 lock이 걸리는 시간을 최소화한다는 점이다.
- 트랜잭션을 커밋해서 영속성 컨텍스트를 플러시하기 전까지는 데이터베이스에 데이터를 등록, 수정, 삭제하지 않는다.
- 따라서 커밋 직전까지 데이터베이스 row에 lock을 걸지 않는다.
✔️ 복습하기
- 트랜잭션 롤백 시 왜 영속성 컨텍스트를 초기화해야 하는지?
- N+1 문제가 발생하는 이유와 해결 방법은?

이 글은 『 자바 ORM 표준 JPA 프로그래밍』 책을 학습한 내용을 정리한 것입니다.
'학습일지 > Java' 카테고리의 다른 글
| [스터디10] 15. 트랜잭션과 락, 2차 캐시 (0) | 2025.12.08 |
|---|---|
| [스터디10] 13. 컬렉션과 부가 기능 (0) | 2025.11.01 |
| [스터디10] 12. 웹 애플리케이션과 영속성 관리 (0) | 2025.10.22 |
| [스터디10] 10. 객체지향 쿼리 언어 (0) | 2025.09.04 |
| [스터디10] 09. 값 타입 (0) | 2025.08.21 |
Comments