🚀
Incheol's TECH BLOG
  • Intro
  • Question & Answer
    • JAVA
      • JVM
      • String, StringBuffer, StringBuilder
      • JDK 17일 사용한 이유(feat. JDK 8 이후 훑어보기)
      • 스택 오버 플로우(SOF)
      • 블럭킹 | 논블럭킹 | 동기 | 비동기
      • 병렬처리를 이용한 이미지 리사이즈 개선
      • heap dump 분석하기 (feat. OOM)
      • G1 GC vs Z GC
      • JIT COMPILER
      • ENUM
      • STATIC
      • Thread(쓰레드)
      • hashCode()와 equals()
      • JDK 8 특징
      • break 와 continue 사용
      • STREAM
      • Optional
      • 람다와 클로저
      • Exception(예외)
      • Garbage Collector
      • Collection
      • Call by Value & Call by Reference
      • 제네릭(Generic)
    • SPRING
      • Spring 특징
      • N+1 문제
      • 테스트 코드 어디까지 알아보고 오셨어요?
      • 테스트 코드 성능 개선기
      • RestTemplate 사용시 주의사항
      • 동시성 해결하기(feat. TMI 주의)
      • redisson trylock 내부로직 살펴보기
      • DB 트래픽 분산시키기(feat. Routing Datasource)
      • OSIV
      • @Valid 동작 원리
      • mybatis @Builder 주의사항
      • 스프링 클라우드 컨피그 갱신 되지 않는 이슈(feat. 서비스 디스커버리)
      • ImageIO.read 동작하지 않는 경우
      • 카프카 transaction 처리는 어떻게 해야할까?
      • Spring Boot 특징
      • Spring 5 특징
      • JPA vs MyBatis
      • Filter와 Interceptor
      • 영속성 컨텍스트(Persistence Context)
      • @Transactional
      • @Controlleradvice, @ExceptionHandler
      • Spring Security
      • Dispatcher Servlet
      • @EnableWebMvc
      • Stereo Type(스테레오 타입)
      • AOP
      • JPA Repository 규칙
    • DATABASE
      • Database Index
      • SQL vs NoSQL
      • DB 교착상태
      • Isolation level
      • [MySQL] 이모지 저장은 어떻게 하면 좋을까?
      • SQL Hint
      • JOIN
    • INFRA
      • CLOUD COMPUTING
      • GIT
      • DOCKER
      • 카프카 찍먹하기 1부
      • 카프카 찍먹하기 2부 (feat. 프로듀서)
      • 카프카 찍먹하기 3부 (feat. 컨슈머)
      • JENKINS
      • POSTMAN
      • DNS 동작 원리
      • ALB, NLB,ELB 차이는?
      • 카프카 파티션 주의해서 사용하자
      • DEVOPS
      • JWT
      • OSI 7 Layer
      • MSA
      • 서비스 디스커버리는 어떻게 서비스 등록/해제 하는걸까?
      • 핀포인트 사용시 주의사항!! (feat 로그 파일 사이즈)
      • AWS EC2 도메인 설정 (with ALB)
      • ALB에 SSL 설정하기(feat. ACM)
      • 람다를 활용한 클라우드 와치 알림 받기
      • AWS Personalize 적용 후기… 😰
      • CloudFront를 활용한 S3 성능 및 비용 개선
    • ARCHITECTURE
      • 객체지향과 절차지향
      • 상속보단 합성
      • SOLID 원칙
      • 캡슐화
      • DDD(Domain Driven Design)
    • COMPUTER SCIENCE
      • 뮤텍스와 세마포어
      • Context Switch
      • REST API
      • HTTP HEADER
      • HTTP METHOD
      • HTTP STATUS
    • CULTURE
      • AGILE(Feat. 스크럼)
      • 우리는 성장 할수 있을까? (w. 함께 자라기)
      • Expert Beginner
    • SEMINAR
      • 2022 INFCON 후기
        • [104호] 사이드 프로젝트 만세! - 기술만큼 중요했던 제품과 팀 성장기
        • [102호] 팀을 넘어서 전사적 협업 환경 구축하기
        • [103호] 코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
        • [105호] 실전! 멀티 모듈 프로젝트 구조와 설계
        • [105호] 지금 당장 DevOps를 해야 하는 이유
        • [102호] (레거시 시스템) 개편의 기술 - 배달 플랫폼에서 겪은 N번의 개편 경험기
        • [102호] 서버비 0원, 클라우드 큐 도입으로 해냈습니다!
  • STUDY
    • 오브젝트
      • 1장 객체, 설계
      • 2장 객체지향 프로그래밍
      • 3장 역할, 책임, 협력
      • 4장 설계 품질과 트레이드 오프
      • 5장 책임 할당하기
      • 6장 메시지와 인터페이스
      • 7징 객체 분해
      • 8장 의존성 관리하기
      • 9장 유연한 설계
      • 10장 상속과 코드 재사용
      • 11장 합성과 유연한 설계
      • 12장 다형성
      • 13장 서브클래싱과 서브타이핑
      • 14장 일관성 있는 협력
      • 15장 디자인 패턴과 프레임워크
      • 마무리
    • 객체지향의 사실과 오해
      • 1장 협력하는 객체들의 공동체
      • 2장 이상한 나라의 객체
      • 3장 타입과 추상화
      • 4장 역할, 책임, 협력
    • JAVA ORM JPA
      • 1장 JPA 소개
      • 2장 JPA 시작
      • 3장 영속성 관리
      • 4장 엔티티 매핑
      • 5장 연관관계 매핑 기초
      • 6장 다양한 연관관계 매핑
      • 7장 고급 매핑
      • 8장 프록시와 연관관계 관리
      • 9장 값 타입
      • 10장 객체지향 쿼리 언어
      • 11장 웹 애플리케이션 제작
      • 12장 스프링 데이터 JPA
      • 13장 웹 애플리케이션과 영속성 관리
      • 14장 컬렉션과 부가 기능
      • 15장 고급 주제와 성능 최적화
      • 16장 트랜잭션과 락, 2차 캐시
    • 토비의 스프링 (3.1)
      • 스프링의 이해와 원리
        • 1장 오브젝트와 의존관계
        • 2장 테스트
        • 3장 템플릿
        • 4장 예외
        • 5장 서비스 추상화
        • 6장 AOP
        • 8장 스프링이란 무엇인가?
      • 스프링의 기술과 선택
        • 5장 AOP와 LTW
        • 6장 테스트 컨텍스트 프레임워크
    • 클린코드
      • 1장 깨끗한 코드
      • 2장 의미 있는 이름
      • 3장 함수
      • 4장 주석
      • 5장 형식 맞추기
      • 6장 객체와 자료 구조
      • 9장 단위 테스트
    • 자바 트러블슈팅(with scouter)
      • CHAP 01. 자바 기반의 시스템에서 발생할 수 있는 문제들
      • CHAP 02. scouter 살펴보기
      • CHAP 03. scouter 설정하기(서버 및 에이전트)
      • CHAP 04. scouter 클라이언트에서 제공하는 기능들
      • CHAP 05. scouter XLog
      • CHAP 06. scouter 서버/에이전트 플러그인
      • CHAP 07. scouter 사용 시 유용한 팁
      • CHAP 08. 스레드 때문에(스레드에서) 발생하는 문제들
      • CHAP 09. 스레드 단면 잘라 놓기
      • CHAP 10. 잘라 놓은 스레드 단면 분석하기
      • CHAP 11. 스레드 문제
      • CHAP 12. 메모리 때문에 발생할 수 있는 문제들
      • CHAP 13. 메모리 단면 잘라 놓기
      • CHAP 14. 잘라 놓은 메모리 단면 분석하기
      • CHAP 15. 메모리 문제(Case Study)
      • CHAP 24. scouter로 리소스 모니터링하기
      • CHAP 25. 장애 진단은 이렇게 한다
      • 부록 A. Fatal error log 분석
      • 부록 B. 자바 인스트럭션
    • 테스트 주도 개발 시작하기
      • CHAP 02. TDD 시작
      • CHAP 03. 테스트 코드 작성 순서
      • CHAP 04. TDD/기능 명세/설계
      • CHAP 05. JUnit 5 기초
      • CHAP 06. 테스트 코드의 구성
      • CHAP 07. 대역
      • CHAP 08. 테스트 가능한 설계
      • CHAP 09. 테스트 범위와 종류
      • CHAP 10. 테스트 코드와 유지보수
      • 부록 A. Junit 5 추가 내용
      • 부록 C. Mockito 기초 사용법
      • 부록 D. AssertJ 소개
    • KOTLIN IN ACTION
      • 1장 코틀린이란 무엇이며, 왜 필요한가?
      • 2장 코틀린 기초
      • 3장 함수 정의와 호출
      • 4장 클래스, 객체, 인터페이스
      • 5장 람다로 프로그래밍
      • 6장 코틀린 타입 시스템
      • 7장 연산자 오버로딩과 기타 관례
      • 8장 고차 함수: 파라미터와 반환 값으로 람다 사용
      • 9장 제네릭스
      • 10장 애노테이션과 리플렉션
      • 부록 A. 코틀린 프로젝트 빌드
      • 부록 B. 코틀린 코드 문서화
      • 부록 D. 코틀린 1.1과 1.2, 1.3 소개
    • KOTLIN 공식 레퍼런스
      • BASIC
      • Classes and Objects
        • Classes and Inheritance
        • Properties and Fields
    • 코틀린 동시성 프로그래밍
      • 1장 Hello, Concurrent World!
      • 2장 코루틴 인 액션
      • 3장 라이프 사이클과 에러 핸들링
      • 4장 일시 중단 함수와 코루틴 컨텍스트
      • 5장 이터레이터, 시퀀스 그리고 프로듀서
      • 7장 스레드 한정, 액터 그리고 뮤텍스
    • EFFECTIVE JAVA 3/e
      • 객체 생성과 파괴
        • 아이템1 생성자 대신 정적 팩터리 메서드를 고려하라
        • 아이템2 생성자에 매개변수가 많다면 빌더를 고려하라
        • 아이템3 private 생성자나 열거 타입으로 싱글턴임을 보증하라
        • 아이템4 인스턴스화를 막으려거든 private 생성자를 사용하라
        • 아이템5 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
        • 아이템6 불필요한 객체 생성을 피하라
        • 아이템7 다 쓴 객체 참조를 해제하라
        • 아이템8 finalizer와 cleaner 사용을 피하라
        • 아이템9 try-finally보다는 try-with-resources를 사용하라
      • 모든 객체의 공통 메서드
        • 아이템10 equals는 일반 규약을 지켜 재정의하라
        • 아이템11 equals를 재정의 하려거든 hashCode도 재정의 하라
        • 아이템12 toString을 항상 재정의하라
        • 아이템13 clone 재정의는 주의해서 진행해라
        • 아이템14 Comparable을 구현할지 고려하라
      • 클래스와 인터페이스
        • 아이템15 클래스와 멤버의 접근 권한을 최소화하라
        • 아이템16 public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
        • 아이템17 변경 가능성을 최소화하라
        • 아이템18 상속보다는 컴포지션을 사용하라
        • 아이템19 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
        • 아이템20 추상 클래스보다는 인터페이스를 우선하라
        • 아이템21 인터페이스는 구현하는 쪽을 생각해 설계하라
        • 아이템22 인터페이스 타입을 정의하는 용도로만 사용하라
        • 아이템23 태그 달린 클래스보다는 클래스 계층구조를 활용하라
        • 아이템24 멤버 클래스는 되도록 static으로 만들라
        • 아이템25 톱레벨 클래스는 한 파일에 하나만 담으라
      • 제네릭
        • 아이템26 로 타입은 사용하지 말라
        • 아이템27 비검사 경고를 제거하라
        • 아이템28 배열보다는 리스트를 사용하라
        • 아이템29 이왕이면 제네릭 타입으로 만들라
        • 아이템30 이왕이면 제네릭 메서드로 만들라
        • 아이템31 한정적 와일드카드를 사용해 API 유연성을 높이라
        • 아이템32 제네릭과 가변인수를 함께 쓸 때는 신중하라
        • 아이템33 타입 안전 이종 컨테이너를 고려하라
      • 열거 타입과 애너테이션
        • 아이템34 int 상수 대신 열거 타입을 사용하라
        • 아이템35 ordinal 메서드 대신 인스턴스 필드를 사용하라
        • 아이템36 비트 필드 대신 EnumSet을 사용하라
        • 아이템37 ordinal 인덱싱 대신 EnumMap을 사용하라
        • 아이템38 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
        • 아이템 39 명명 패턴보다 애너테이션을 사용하라
        • 아이템40 @Override 애너테이션을 일관되게 사용하라
        • 아이템41 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
      • 람다와 스트림
        • 아이템46 스트림에는 부작용 없는 함수를 사용하라
        • 아이템47 반환 타입으로는 스트림보다 컬렉션이 낫다
        • 아이템48 스트림 병렬화는 주의해서 적용하라
      • 메서드
        • 아이템49 매개변수가 유효한지 검사하라
        • 아이템50 적시에 방어적 본사본을 만들라
        • 아이템53 가변인수는 신중히 사용하라
        • 아이템 54 null이 아닌, 빈 컬렉션이나 배열을 반환하라
        • 아이템56 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • 일반적인 프로그래밍 원칙
        • 아이템56 공개된 API 요소에는 항상 문서화 주석을 작성하라
        • 아이템57 지역변수의 범위를 최소화하라
        • 아이템 60 정확한 답이 필요하다면 float와 double은 피하라
      • 예외
        • 아이템 73 추상화 수준에 맞는 예외를 던지라
        • 아이템 74 메서드가 던지는 모든 예외를 문서화하라
      • 동시성
        • 아이템78 공유 중인 가변 데이터는 동기화해 사용하라
        • 아이템79 과도한 동기화는 피하라
        • 아이템 80 스레드보다는 실행자, 태스크, 스트림을 애용하라
      • 직렬화
        • 아이템 87 커스텀 직렬화 형태를 고려해보라
    • Functional Programming in Java
      • Chap 01. 헬로, 람다 표현식
      • Chap 02. 컬렉션의 사용
      • Chap 03. String, Comparator, 그리고 filter
      • Chap 04. 람다 표현식을 이용한 설계
      • CHAP 05. 리소스를 사용한 작업
      • CHAP 06. 레이지
      • CHAP 07. 재귀 호출 최적화
      • CHAP 08. 람다 표현식의 조합
      • CHAP 09. 모든 것을 함께 사용해보자
      • 부록 1. 함수형 인터페이스의 집합
      • 부록 2. 신택스 오버뷰
    • 코틀린 쿡북
      • 2장 코틀린 기초
      • 3장 코틀린 객체지향 프로그래밍
      • 4장 함수형 프로그래밍
      • 5장 컬렉션
      • 6장 시퀀스
      • 7장 영역 함수
      • 9장 테스트
      • 10장 입력/출력
      • 11장 그 밖의 코틀린 기능
    • DDD START!
      • 1장 도메인 모델 시작
      • 2장 아키텍처 개요
      • 3장 애그리거트
      • 4장 리포지터리와 모델구현(JPA 중심)
      • 5장 리포지터리의 조회 기능(JPA 중심)
      • 6장 응용 서비스와 표현 영역
      • 7장 도메인 서비스
      • 8장 애그리거트 트랜잭션 관리
      • 9장 도메인 모델과 BOUNDED CONTEXT
      • 10장 이벤트
      • 11장 CQRS
    • JAVA 8 IN ACTION
      • 2장 동작 파라미터화 코드 전달하기
      • 3장 람다 표현식
      • 4장 스트림 소개
      • 5장 스트림 활용
      • 6장 스트림으로 데이터 수집
      • 7장 병렬 데이터 처리와 성능
      • 8장 리팩토링, 테스팅, 디버깅
      • 9장 디폴트 메서드
      • 10장 null 대신 Optional
      • 11장 CompletableFuture: 조합할 수 있는 비동기 프로그래밍
      • 12장 새로운 날짜와 시간 API
      • 13장 함수형 관점으로 생각하기
      • 14장 함수형 프로그래밍 기법
    • 객체지향과 디자인패턴
      • 객체 지향
      • 다형성과 추상 타입
      • 재사용: 상속보단 조립
      • 설계 원칙: SOLID
      • DI와 서비스 로케이터
      • 주요 디자인 패턴
        • 전략패턴
        • 템플릿 메서드 패턴
        • 상태 패턴
        • 데코레이터 패턴
        • 프록시 패턴
        • 어댑터 패턴
        • 옵저버 패턴
        • 파사드 패턴
        • 추상 팩토리 패턴
        • 컴포지트 패턴
    • NODE.JS
      • 1회차
      • 2회차
      • 3회차
      • 4회차
      • 6회차
      • 7회차
      • 8회차
      • 9회차
      • 10회차
      • 11회차
      • 12회차
      • mongoose
      • AWS란?
    • SRPING IN ACTION (5th)
      • Chap1. 스프링 시작하기
      • Chap 2. 웹 애플리케이션 개발하기
      • Chap 3. 데이터로 작업하기
      • Chap 4. 스프링 시큐리티
      • Chap 5. 구성 속성 사용하기
      • Chap 6. REST 서비스 생성하기
      • Chap 7. REST 서비스 사용하기
      • CHAP 8 비동기 메시지 전송하기
      • Chap 9. 스프링 통합하기
      • CHAP 10. 리액터 개요
      • CHAP 13. 서비스 탐구하기
      • CHAP 15. 실패와 지연 처리하기
      • CHAP 16. 스프링 부트 액추에이터 사용하기
    • 스프링부트 코딩 공작소
      • 스프링 부트를 왜 사용 해야 할까?
      • 첫 번째 스프링 부트 애플리케이션 개발하기
      • 구성을 사용자화 하기
      • 스프링부트 테스트하기
      • 액추에이터로 내부 들여다보기
    • ANGULAR 4
      • CHAPTER 1. A gentle introduction to ECMASCRIPT 6
      • CHAPTER 2. Diving into TypeScript
      • CHAPTER 3. The wonderful land of Web Components
      • CHAPTER 4. From zero to something
      • CHAPTER 5. The templating syntax
      • CHAPTER 6. Dependency injection
      • CHAPTER 7. Pipes
      • CHAPTER 8. Reactive Programming
      • CHAPTER 9. Building components and directives
      • CHAPTER 10. Styling components and encapsulation
      • CHAPTER 11. Services
      • CHAPTER 12. Testing your app
      • CHAPTER 13. Forms
      • CHAPTER 14. Send and receive data with Http
      • CHAPTER 15. Router
      • CHAPTER 16. Zones and the Angular magic
      • CHAPTER 17. This is the end
    • HTTP 완벽 가이드
      • 게이트웨이 vs 프록시
      • HTTP Header
      • REST API
      • HTTP Method 종류
        • HTTP Status Code
      • HTTP 2.x
  • REFERENCE
    • TECH BLOGS
      • 어썸데브블로그
      • NAVER D2
      • 우아한 형제들
      • 카카오
      • LINE
      • 스포카
      • 티몬
      • NHN
      • 마켓컬리
      • 쿠팡
      • 레진
      • 데일리 호텔
      • 지그재그
      • 스타일쉐어
      • 구글
      • 야놀자
    • ALGORITHM
      • 생활코딩
      • 프로그래머스
      • 백준
      • 알고스팟
      • 코딜리티
      • 구름
      • 릿코드
Powered by GitBook
On this page
  • 예외 처리
  • 스프링 프레임워크에 JPA 예외 변환기 적용
  • 트랜잭션 롤백 시 주의사항
  • 엔티티 비교
  • 프록시 심화 주제
  • 영속성 컨텍스트와 프록시
  • 상속관계와 프록시
  • 성능 최적화
  • N+1 문제
  • N+1 문제 해결 방법
  • 읽기 전용 쿼리의 성능 최적화
  • 배치 처리
  • JPA 페이징 배치 처리
  • 하이버네이트 scroll 사용
  • SQL 쿼리 힌트 사용
  • 정리
  • 질문

Was this helpful?

  1. STUDY
  2. JAVA ORM JPA

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

자바 ORM 표준 JPA 프로그래밍 15장을 요약한 내용 입니다.

예외 처리

JPA 표준 예외들은 javax.persistence.PersistenceException의 자식 클래스다. 그리고 이 예외 클래스는 RuntimeException의 자식이다. 따라서 JPA 예외는 모두 언체크 예외다. JPA 표준 예외는 크게 2가지로 나눌 수 있다.

  • 트랜잭션 롤백을 표시하는 예외

  • 트랜잭션 롤백을 표시하지 않는 예외

트랜잭션 롤백을 표시하는 예

예

설

javax.persistence.EntityExistException

EntityManager.persist(...) 호출 시 이미 같은 엔티티가 있으면 발생한다.

java.persistence.EntityNotFoundException

EntityManager.getReference(...)를 호출했는데 실제 사용 시 엔티티가 존재하지 않으면 발생. refersh(...), lock(...)에서도 발생한다.

javax.persistence.OptimisticLockException

낙관적 락 충돌 시 발생한다.

javax.persistence.PessimisticLockException

비관적 락 충돌 시 발생한다.

javax.persistence.RollbackException EntityTransaction.commit()

실패 시 발생, 롤백이 표시되어 있는 트랜잭션 커밋 시에도 발생한다.

javax.persistence.TransactionRequiredException

트랜잭션이 필요할 때 트랜잭션이 없으면 발생. 트랜잭션 없이 엔티티를 변경할 때 주로 발생한다.

트랜잭션 롤백을 표시하지 않는 예

예외

설명

javax.persistence.NoResultException

Query.getSingleResult() 호출 시 결과가 하나도 없을 때 발생한다

javax.persistence.NonUniqueResultException

Query.getSingleResult() 호출 시 결과가 둘 이상일 때 발생한다.

javax.persistence.LockTimeoutException

비관적 락에서 시간 초과 시 발생한다

javax.persistence.QueryTimeoutException

쿼리 실행 시간 초과 시 발생한다.

JPA 예외를 스프링 예외로 변

JPA 예외

스프링 변환 예외

javax.persistence.PersistenceException

org.springgramework.orm.jpa.JpaSystemException

javax.persistence.NoResultException

org.springfreamework.dao.ExptyResultDataAccessException

javax.persistence.NonUniqueResultException

org.springframework.dao.IncorrectrResultSizeDataAccessException

javax.persistence.LockTimeoutException

org.springframework.dao.CannotAcquireLockException

javax.persistence.QueryTimeoutException

org.springframework.dao.DataIntegrityViolationException

javax.persistence.EntityExistException

org.springframework.orm.jpa.JpaObjectRetrievalFailurewException

javax.persistence.EntityNotFoundException

org.springframework.orm.jpa.JpaObjectRetrivalFailtueException

javax.persistence.OptimisticLockException

org.springframework.dao.JpaOptimisticLockingFailureException

javax.persistence.PessimisticLockException

org.springframework.dao.PessimisticLockingFailureException

javax.persistence.TransactionRequiredException

org.springframework.dao.InvalidDataAccessApiUsageException

javax.persistence.RollbackException

org.springframework.transaction.TransactionSystemException

JPA 예외를 스프링 예외로 변경 추

JPA 예외

스프링 변환 예외

java.lang.IllegalStateException

org.springframework.dao.InvalidDataAccessApiUsageException

java.lang.IllegalArgumentException

org.springframework.dao.InvalidDataAccessApiUsageException

스프링 프레임워크에 JPA 예외 변환기 적용

JPA 예외를 스프링 프레임워크가 제공하는 추상화된 예외로 변경하려면 PersistenceExceptionTransactionPostProcessor를 스프링 빈으로 등록하면 된다. 이것은 @Repository 어노테이션을 사용한 곳에 예외 변환 AOP를 적용해서 JPA 예외를 스프링 프레임워크가 추상화한 예외로 변환해준다.

<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
	return new PersistenceExceptionTranslationPostProcessor();
}

만약 예외를 변환하지 않고 그대로 반환하고 싶으면 다음처럼 throws 절에 그대로 반환할 JPA 예외나 JPA 예외의 부모 클래스를 직접 명시 하면 된다.

@Repository
public class NoResultExceptionTestService {
	@PersistenceContext EntityManager em;

	public Member findMember() throws javax.persistence.NoResultException {
		return em.creteQuery("select m from Member m", Member.class).getSingleResult();
	}
}

트랜잭션 롤백 시 주의사항

트랜잭션을 롤백하는 것은 데이터베이스의 반영 사항만 롤백 하는 것이지 수정한 자바 객체까지 원 상태로 복구해주지는 않는다. 예를 들어 엔티티를 조회해서 수정하는 중에 문제가 있어서 트랜잭션을 롤백하면 데이터베이스의 데이터는 원래대로 복구되지만 객체는 수정된 상태로 영속성 컨텍스트에 남아 있다. 따라서 새로운 영속성 컨텍스트를 생성해서 사용하거나 EntityManager.clear()를 호출해서 영속성 컨텍스트를 초기화한 다음에 사용해야 한다.

기본 전략인 트랜잭션당 영속성 컨텍스트 전략은 문제가 발생하면 트랜잭션 AOP 종료 시점에 트랜잭션을 롤백하면서 영속성 컨텍스트도 함께 종료하므로 문제가 발생하지 않는다.

문제는 OSIV처럼 영속성 컨텍스트의 범위를 트랜잭션 범위보다 넓게 사용해서 여러 트랜잭션이 하나의 영속성 컨텍스트를 사용할 때 발생한다. 스프링 프레임워크는 영속성 컨텍스트의 범위를 트랜잭션의 범위보다 넓게 설정하면 트래잭션 롤백시 영속성 컨텍스트를 초기화해서 잘못된 영속성 컨텍스트를 사용하는 문제를 예방한다.

엔티티 비교

영속성 컨텍스트를 통해 데이터를 저장하거나 조회하면 1차 캐시에 엔티티가 저장된다. 이 1차 캐시 덕분에 변경 감지 기능도 동작하고, 이름 그대로 1차 캐시로 사용 되어서 데이터베이스를 통하지 않고 데이터를 바로 조회할 수도 있다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:appConfig.xml")
@Transactional
public class MemberServiceTest {

	@Test
	public void 회원가입() throws Exception {
		// Given
		Member member = new Member("kim");

		// When
		Long saveId = memberService.join(member);

		// Then
		Member findMember = memberRepository.findOne(saveId);
		assertTrue(member == findMember); // true 참조값 비교
	}
}

이것은 같은 트랜잭션 범위에 있으므로 같은 영속성 컨텍스트를 사용하기 때문이다. 따라서 영속성 컨텍스트가 같으면 엔티티를 비교할 때 다음 3가지 조건을 모두 만족한다.

  • 동일성: == 비교가 같다

  • 동등성 : equals() 비교가 같다

  • 데이터베이스 동등성 : @Id인 데이터베이스 식별자가 같다

정리하자면 동일성 비교는 같은 영속성 컨텍스트의 관리를 받는 영속 상태의 엔티티에만 적용할 수 있다. 그렇지 않을 때는 비즈니스 키를 사용한 동등성 비교를 해야 한다.

프록시 심화 주제

프록시는 원본 엔티티를 상속받아서 만들어지므로 엔티티를 사용하는 클라이언트는 엔티티가 프록시인지 아니면 원본 엔티티인지 구분하지 않고 사용할 수 있다. 이로 인해 예상하지 못한 문제들이 발생하기도 하는데, 어떤 문제가 발생하고 어떻게 해결해야 하는지 알아보자

영속성 컨텍스트와 프록시

엔티티의 동등성을 비교하려면 비즈니스 키를 사용해서 equals() 메소드를 오버라이딩하고 비교하면 된다. 그런데 IDE나 외부 라이브러리를 사용해서 구현한 equals() 메소드로 엔티티를 비교할 때, 비교 대상이 원본 엔티티면 문제가 없지만 프록시면 문제가 발생할 수 있다.

@Entity
public class Member {
	@Id
	private String id;
	private String name;

	...

	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (this.getClass() != obj.getClass()) return false; // 1

		Member member = (Member) obj;

		if (this.name != null ? !this.name.equals(member.name) : member.name != null) //
			return false;

		return true;
	}
}

테스트 코드를 작성해보자

@Test
public void 프록시와_동등성비교() {
        Member saveMember = new Member("member1", "회원1");
        em.persist(saveMember);
        em.flush();
        em.clear();
        
        Member newMember = new Member("member1", "회원1");
        Member refMember = em.getReference(Member.class, "member1");
        
        Assert.assertTrue(newMember.equals(refMember)); // false
}

왜 이런 문제가 발생할까?

  • 프록시는 원본을 상속받은 자식 타입이므로 프록시의 타입을 비교할 때는 == 비교가 아닌 instanceof를 사용해야 한다,. 따라서 다음처럼 변경해야 한다.

    if (!(obj instanceof Member)) return false;
  • memner.name을 보면 프록시의 멤버변수에 직접 접근하는데 이 부분을 주의깊게 봐야 한다. 프록시는 실제 데이터를 가지고 있지 않기 때문에 프록시의 멤버 변수에 직접 접근하면 아무값도 조회할 수 없다. 그러므로 프록시의 데이터를 조회할 때는 접근자(Getter)를 사용해야 한다.

상속관계와 프록시

상속 관계를 프록시로 조회할 때 발생할 수 있는 문제점과 해결방안을 알아보자

위와 같은 구조의 클래스 모델을 생성할 경우 프록시를 부모 타입으로 조회하면 문제가 발생한다.

@Test
public void 부모타입으로_프록시조회() {
	//테스트 데이터 준비
	Book saveBook = new Book();
	saveBook.setName("jpaBook");
	saveBook.setAuthor("kim");
	em.persist(saveBook);

	em.flush();
	em.clear();

	//테스트 시작
	Item proxyItem = em.getReference(Item.class, saveBook.getId());
	System.out.println("proxyItem = " + proxyItem.getClass());

	if (proxyItem instanceof Book) {
		System.out.println("proxyItem instanceof Book");
		Book book = (Book) proxyItem;
		System.out.println("책 저자 = " + book.getAuthor());
	}

	//결과 검증
	Assert.assertFalse(proxyItem.getClass() == Book.class);
	Assert.assertFalse(proxyItem instanceof Book);
	Assert.assertTrue(proxyItem instanceof Item);
}

그런데 출력 결과를 보면 기대와는 다르게 저자가 출력되지 않은 것을 알 수 있다.

왜 원하는 출력값이 다를까?

실제 조회된 엔티티는 Book이므로 Book 타입을 기반으로 원본 엔티티 인스턴스가 생성된다. 그런데 em.getReference() 메소드에서 Item 엔티티를 대상으로 조회 했으므로 프록시인 proxyItem은 Item 타입을 기반으로 만들어진다. 이런 이유로 다음 연산이 기대와 다르게 false를 반환한다. 왜냐하면 proxyItem은 Item&Proxy 타입이고 이 타입은 Book 타입과 관계가 없기 때문이다.

proxyItem instanceof Book // false

따라서 직접 다운캐스팅을 해도 문제가 발생한다.

Book book = (Book) proxyItem; // java.lang.ClassCastException

그렇다면 상속관계에서 발생하는 프록시 문제를 어떻게 해결해야 할까?

  • JPQL로 대상 직접 조회

    Book jpqlBook = em.createQuery
    	("select b from Book b where b.id=:bookId", Book.class)
    	.setParameter("bookId", item.getId())
    	.getSingleResult();
  • 프록시 벗기기

    	...
    	Item item = orderItem.getItem();
    	Item unProxyItem = unProxy(item);
    	
    	if (unProxyItem instanceof Book) {
    		System.out.println("proxyItem instanceod Book");
    		Book book = (Book) unproxyItem;
    		System.out.println("책 저자 = " + book.getAuthor());
    	}
    	
    	Assert.assertTrue(item != unProxyItem);
    }
    
    //하이버네이트가 제공하는 프록시에서 원본 엔티티를 찾는 기능을 사용하는 메소드
    public static <T> T unProxy(Object entity){
    	if(entity instanceof HibernateProxy) {
    		entity = ((HibernateProxy) entity)
    				.getHibernateLazyInitializer()
    				.getImplementation();
    	}
    	return (T) entity;
    }

    그런데 이 방법은 프록시에서 원본 엔티티를 직접 꺼내기 때문에 프록시와 원본 엔티티의 동일성 비교가 실패한다는 문제점이 있다.

    item == unProxyItem // false
  • 기능을 위한 별도의 인터페이스 제공

    public interface TitleView {
    	String getTitle();
    }
    
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "DTYPE")
    public abstract class Item implements TitleView {
    	@Id @GeneratedValue
    	@Column(name = "ITEM_ID")
    	private Long id;
    	
    	...
    }
  • 비지터 패턴

    public interface Visitor {
    	void visit(Book book);
    	void visit(Album album);
    	void visit(Movie movie);
    }
    public class PrintVisitor implements Visitor {
    	@Override
    	public void visit(Book book) {
    		//넘어오는 book은 Proxy가 아닌 원본 엔티티다. 
    		System.out.println("book.class = " + book.getClass());
    	}
    	
    	@Override
    	void visit(Album album) {...}
    	@Override
    	void visit(Movie movie) {...}
    @Entity
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "DTYPE")
    public abstract class Item {
    
    	@Id @GeneratedValue
    	@Column(name = "ITEM_ID")
    	private Long id;
    
    	...
    
    	public abstract void accept(Visitor visitor);
    }
    
    @Entity
    @DiscriminatorValue("B")
    public class Book extends Item {
    
    	...
    	@Override
    	public void accept(Visitor visitor){
    		visitor.visit(this);
    	}
    }
    @Test
    public void 상속관계와_프록시_VisitorPattern() {
    	...
    	OrderItem orderItem = em.find(OrderItem.class, orderItemId);
    	Item item = orderItem.getItem();
    
    	//PrintVisitor
    	item.accept(new PrintVisitor());
    }
    public class PrintVisitor implements Visitor {
    	public void visit(Book book) {
    		//넘어오는 book은 Proxy가 아닌 원본 엔티티다. 
    		System.out.println("book.class = " + book.getClass());
    
    	}
    	public void visit(Album album) {...}
    	public void visit(Movie movie) {...}
    }

    비지터 패턴의 장점은 다음과 같다.

    • 프록시에 대한 걱정 없이 안전하게 원본 엔티티에 접근할 수 있다.

    • instanceof와 타입 캐스팅 없이 코드를 구현할 수 있다.

    • 알고리즘과 객체 주고를 분리해서 구조를 수정하지 않고 새로운 동작을 추가할 수 있다.

    단점은 다음과 같다.

    • 너무 복잡하고 더블 디스패치를 사용하기 때문에 이해하기 어렵다.

    • 객체 구조가 변경되면 모든 Visitor를 수정해야 한다.

성능 최적화

JPA로 애플리케이션을 개발할 때 발생하는 다양한 성능 문제와 해결 방안을 알아보자.

N+1 문제

@Entity
public class Member {

	@Id @GeneratedValue
	private Long id;

	@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
	private List<Order> orders = new ArrayList<Order>();

	...
}
@Entity
public class Order {
	@Id @GeneratedValue
	private Long id;

	@ManyToOne
	private Member member;
	...
}

즉시 로딩과 N+1

em.find() 메소드로 조회하면 즉시 로딩으로 설정한 주문정보도 함께 조회한다.

em.find(Member.class, id);

// 결과값
SELECT M.*, O.*
FROM
	MEMBER M
OUTER JOIN ORDERS O ON M.ID = O.MEMNBER_ID

문제는 JPQL을 사용할 때 발생한다.

List<Member> members = 
	em.createQuery("select m from Member m", Member.class)
	.getResultList();

// 결과 SQL 로그
SELECT * FROM MEMNER
SELECT * ORDERS WHERE MEMBER_ID =1 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =2 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =3 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =4 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =5 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =6 // 회원과 연관된 주문

지연 로딩과 N+1

for (Member member : members) {
	//지연 로딩 초기화
	System.out.println("member = " + member.getOrders().size();
}

// 결과 SQL 로그
SELECT * ORDERS WHERE MEMBER_ID =1 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =2 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =3 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =4 // 회원과 연관된 주문
SELECT * ORDERS WHERE MEMBER_ID =5 // 회원과 연관된 주문

N+1 문제 해결 방법

페치 조인 사용

페치 조인은 SQL 조인을 사용해서 연관된 엔티티를 함께 조회하므로 N+1 문제가 발생하지 않는다.

select m from Member m join fetch m.orders

// 결과 SQL 로그
SELECT M.*, O.* FROM MEMBER M
INNER JOIN ORDERS O ON M.ID=O.MEMBER_ID

참고로 이 예제는 일대다 조인을 했으므로 결과가 늘어나서 중복된 결과가 나타날 수 있다. 따라서 JPQL의 DISTINCT를 사용해서 중복을 제거하는 것이 좋다.

하이버네이트 @BatchSize

BatchSize 어노테이션을 사용하면 연관된 엔티티를 조회할 때 지정한 size만큼 SQL의 IN절을 사용해서 조회한다. 만약 조회한 회원이 10명인데 size=5로 지정하면 2번의 SQL만 추가로 실행한다.

@Entity
public class Member {

	@Id @GeneratedValue
	private Long id;

	@BatchSize(size = 5)
	@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
	private List<Order> orders = new ArrayList<Order>();

	...
}

하이버네이트 @Fetch(FetchMode.SUBSELECT)

FetchMode를 SUBSELECT로 사용하면 연관된 데이터를 조회할 때 서브 쿼리를 사용해서 N+1 문제를 해결할 수 있다.

@Entity
public class Member {

	@Id @GeneratedValue
	private Long id;

	@Fetch(FetchMode.SUBSELECT)
	@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
	private List<Order> orders = new ArrayList<Order>();

	...
}
select m from Member m where m.id > 10

// 결과 SQL 로그
SELECT O FROM ORDERS O
	WHERE O.MEMBER_ID IN (
		SELECT 
			M.ID
		FROM
			MEMBER M
		WHERE M.ID > 10
	}

읽기 전용 쿼리의 성능 최적화

엔티티가 영속성 컨텍스트에 관리되면 1차 캐시부터 변경 감지 까지 얻을 수 있는 혜택이 많다. 하지만 영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관하므로 더 많은 메모리를 사용하는 단점이 있다. 이때는 읽기 전용으로 엔티티를 조회하면 메모리 사용량을 최적화할 수 있다.

@Transactional(readOnly = true)

위와 같이 읽기 전용으로 설정하면 트랜잭션을 커밋해도 영속성 컨텍스트를 플러시하지 않는다. 그러므로 플러시할 때 일어나는 스냅샷 비교와 같은 무거운 로직들을 수행하지 않으므로 성능이 향상된다.

엔티티 매니저의 플러시 설정에는 AUTO, COMMIT 모드만 있고, MANUAL 모드가 없다. 반면에 하이버네이트 세션의 플러시 설정에는 MANUAL 모드가 있다. MANUAL 모드는 강제로 플러시를 호풀하지 않으면 절대 플러시가 발생하지 않는다.

배치 처리

수백만 건의 데이터를 배치 처리해야 하는 상황이라 가정해보자. 일반적인 방식으로 엔티티를 계속 조회하면 영속성 컨텍스트에 아주 많은 엔티티가 쌓이면서 메모리 부족 오류가 발생한다. 따라서 이런 배치 처리는 적절한 단위로 영속성 컨텍스트를 초기화해야 한다. 배치 처리는 아주 많은 데이터를 조회해서 수정한다. 이때 수많은 데이터를 한번에 메모리에 올려둘 수 없어서 2가지 방법을 주로 사용한다.

  • 페이징 처리

  • 커서

JPA 페이징 배치 처리

EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();

tx.begin();

int pageSize = 100;
for(int i =0; i<10; i++){
	List<Product> resultList = em.createQuery("select p from Product p", Product.class)
					.setFirstResult(i * pageSize)
					.setMaxResults(pageSize)
					.getResultList();
	// 비즈니스 로직 실행
	for( Product product : resultList) {
		product.setPrice(product.getPrice() + 100);
	}

	em.flush();
	em.clear();
}

tx.commit();
em.close();

이는 한번에 100건씩 페이징 쿼리로 조회하면서 상품의 가격을 100원씩 증가한다. 그리고 페이지 단위마다 영속성 컨텍스트를 플러시하고 초기화한다.

하이버네이트 scroll 사용

하이버네이트는 scroll이라는 이름으로 JDBC 커서를 지원한다.

EntityTransaction tx = em.getTransaction();
Session session = em.unwrap(Session.class);

tx.begin();
ScrollableResults scroll = session.createQuery("select p from Product p")
					.setCacheMode.IGNORE) // 2차 캐시 기능을 끈다
					.scroll(ScrollMode.FORWARD_ONLY);

int count = 0;

while (scroll.next()) {
	Product p = (Product) scroll.get(0);
	p.setPrice(p.getPrice() + 100);

	count++;
	if(count % 100 ==0) {
		session.flush(); // 플러시
		session.clear(); // 영속성 컨텍스트 초기화
	}
}

tx.commit();
session.close();

scroll은 하이버네이트 전용 기능이므로 먼저 em.unwrap() 메소드를 사용해서 하이버네이트 세션을 구한다. 다음으로 쿼리를 조회하면서 scroll() 메소드로 ScrollableResults 객체를 반환받는다. 이 객체의 next() 메소드를 호출하면 엔티티를 하나씩 조회할 수 있다.

SQL 쿼리 힌트 사용

SQL 힌트를 사용하려면 하이버네이트를 직접 사용해야 한다. SQL 힌트는 하이버네이트 쿼리가 제공하는 addQueryHint() 메소드를 사용한다.

Session session = em.unwrap(Session.class); // 하이버네이트 직접 사용
List<Member> list = session.createQuery("select m from Member m")
				.addQueryHint("FULL (MEMBER)")
				.list();

// 실행 결과
select 
	/*+ FULL (MEMBER) */ m.id, m.name
from
	Member m

정리

  • JPA의 예외는 트랜잭션 롤백을 표시하는 예외와 표시하지 않는 예외로 나눈다. 트랜잭션을 롤백하는 예외는 심각한 예외이므로 트랜잭션을 강제로 커밋해도 커밋되지 않고 롤백된다.

  • 스프링 프레임워크는 JPA의 예외를 스프링 프레임워크가 추상화한 예외로 변환해준다.

  • 프록시를 사용하는 클라이언트는 조회한 엔티티가 프록시인지 아니면 원본 엔티티인지 구분하지 않고 사용할 수 있어야 한다. 하지만 프록시는 기술적인 한계가 있으므로 한계점을 인식하고 사용해야 한다.

  • JPA를 사용할 때는 N+1 문제를 가장 조심해야 한다. N+1 문제는 주로 페치 조인을 사용해서 해결한다.

  • 엔티티를 읽기 전용으로 조회하면 스냅샷을 유지할 필요가 없고 영속성 컨텍스트도 초기화해야 한다.

  • JPA는 SQL 쿼리 힌트를 지원하지 않지만 하이버네이트 구현체를 사용하면 SQL 쿼리 힌트를 사용할 수 있다.

  • 트랜잭션을 지원하는 쓰기 지연 덕분에 SQL 배치 기능을 사용할 수 있다.

질문

예외에 따라 롤백기능이 다름

  • 체크드 → 롤백 안됨

  • 언 체크드 → 롤백됨

비관적 락 해결 방법?

Previous14장 컬렉션과 부가 기능Next16장 트랜잭션과 락, 2차 캐시

Last updated 1 year ago

Was this helpful?