1. 예외 처리 - JPA 예외는 모두 Unchecked Exception
- 트랜잭션 롤백을 표시하는 예외 - 심각한 예외, 복구해선 안됨 (엔티티 중복, 부재, 낙관적 락, 비관적 락 등)
- 트랜잭션 롤백을 표시하지 않는 예외 - 호출 시 결과가 없을때, 시간 초과
JPA 예외들은 스프링 예외로 변경됩니다.
ex)javax.persistence.PersistenceException -> org.springframework.orm.jpa.JpaSystemException 등
2. 엔티티 비교
다른 영속성 컨텍스트라면 DB상의 같은 ROW라 하더라도 "==" 으로 비교할 수 없다. 식별자(ID)를 비교하거나 equals 재정의를 통해 비교해야함
3. 프록시 심화 주제
프록시로 조회한 엔티티의 동일성도 보장하는가?
1. 프록시 조회후, 원본 엔티티 조회 - 원본 엔티티 조회시에 프록시 엔티티 조회 (동일성 보장)
2. 원본 엔티티 조회후, 프록시 조회 - 원본 엔티티 조회 (동일성 보장)
하지만 프록시를 비교할땐 "==" 가 아닌 "instanceof" 사용, 프록시는 원본 엔티티의 자식 타입이기 때문
상속관계와 프록시
프록시를 부모타입으로 조회하면 문제, instacneof, 다운캐스팅 불가
JPQL 직접 조회, 하이버네이트 프록시 벗기기, 별도 인터페이스 사용, 비지터 패턴으로 해결 가능
4. 성능 최적화
1. N+1 문제 (가장 중요) - 즉시로딩 지연로딩 상관 없이 발생
# 해결법
- FETCH JOIN = DB JOIN
- 하이버네이트 @BactchSize
- @Fetch(FetchMode.SUBSELECT) - 연관된 데이터를 조회할때 서브쿼리를 사용해서 해결
2. 읽기 전용 쿼리 성능 최적화
딱 한번만 '읽는' 경우 읽기 전용으로 엔티티를 조회하면 메모리 사용량을 최적화
- 스칼라 타입으로 조회
- 읽기 전용 쿼리 힌트 사용
- 읽기 전용 트랜잭션 @Transactional(readOnly=true) - flush가 수동을 바뀜, 수정 삭제 먹통
- 트랜잭션 밖에서 읽기 @Transactional(propagation = Propagation.NOT_SUPPORTED)
2, 3을 같이 사용하는것이 가장 효과적
3. 배치 처리
너무 많은 양의 데이터를 계속해서 조회하면 영속성 컨텍스트에 너무 많이 쌓여 메모리가 부족해짐. 따라서 이런 배치 처리는 적절한 단위로 초기화 필요. 특히 2차 캐시에 엔티티 보관 X
페이징 처리 ,CURSOR 사용함
5. 트랜잭션의 격리성
격리 수준이 낮을수록 동시성은 증가하지만 문제가 발생합니다.
- READ UNCOMMITTED - 커밋하지 않은 데이터를 읽을 수 있습니다. A 트랜잭션에서 데이터가 수정중일때 B 트랜잭션이 그 데이터를 조회할 수 있습니다. 이 경우 A가 롤백되면 데이터 정합성에 심각한 문제 발생 (DIRTY READ)
- READ COMMITTED - A 트랜잭션이 데이터를 조회중이고 B가 데이터를 수정하면 A 트랜잭션 조회 데이터가 수정됩니다. 반복해서 같은 데이터를 읽을 수 없습니다. (NON-REPEATABLE READ)
- REPEATABLE READ - 데이터가 새로 추가되었을때 반복 조회 시 결과 '집합'이 달라지는 경우 (PHANTOM READ)
- SERIALIZABLE - 최고 격리수준, 하지만 동시성 처리 성능 최악
최근 DB들이 동시성 처리를 위해 락 보다는 MVCC(Multiversion_concurrency_control) 사용
6. 낙관적 락과 비관적 락 기초
영속성 컨텍스트를 활용하면 READ COMMITTED 수준이어도 반복 읽기가 가능
낙관적 락 - 트랜잭션 대부분은 충돌이 나지 않음을 가정, DB의 락기능을 사용하지 않고 JPA가 제공하는 버전 관리기능 사용, 커밋 전까지는 충돌을 알 수 없음
비관적 락 - 트랜잭션의 충돌이 남을 가정하고 우선 락을 걸고 봄, DB의 락 기능 사용
두번의 갱실 분실 문제 - 트랜잭션만으로 해결 X
해결법 3가지 - 마지막 커밋 인정, 최초 커밋 인정, 갱신 내용 병합
@Version - 엔티티를 수정할 때마다 자동으로 증가, 그런데 수정 시 조회 시점의 버전과 다르면 예외 발생 (최초 커밋 인정), jpa가 관리하므로 개발자가 임의로 수정하면 안됨
추천하는 전략은 READ COMMITTED + 낙관적 버전 관리 (두번의 갱신 내역 분실 문제 예방)
# 락을 걸 수 있는 위치
- EntityManager.lock(), find(), refresh()
- Query.setLockMode()
- NamedQuery
낙관적 락
락 옵션을 적용하지 않아도 @Version만 있으면 낙관적 락 적용
NONE - 수정시 버전비교
OPTIMISTIC - 조회시 버전 비교
OPTIMISTIC_FORCE_INCREMENT
비관적 락
엔티티가 아닌 스칼라 타입을 조회할 때도 사용 가능
수정하는 즉시 충돌 확인 가능
PESSIMISTIC_WRITE - 일반적인 옵션, NON-REPEATABLE READ 방지
수정이 동시에 일어나는 경우가 많을 것 같은 경우 비관적 락, 아닌경우 낙관적 락을 사용
7. 2차 캐시
1차 캐시 - 영속성 컨텍스트 내부에 존재, 원래는 트랜잭션을 종료하면 같이 종료되지만 OSIV를 사용하면 요청의 끝까지 유지됨
2차 캐시 - 1차 캐시와 다르게 애플리케이션 범위, 분산캐시나 클러스터링 환경의 캐시는 더 오래 유지도 가능
2차 캐시의 락은...? - 캐시에 객체를 그대로 반환하면 동시성 문제가 발생하고 락을 걸어줘야합니다. 이를 위해서 동시성을 극대화 하려고 캐시한 객체를 직접반환하지 않고 복사본을 반환합니다. 락 비용 > 복사 비용
DB 기본키를 기준으로 캐시하지만 영속성 컨텍스트가 다르면 객체 동일성을 보장하지 않음 (복사본이므로)
# Reference
chatgpt
자바 ORM 표준 JPA 프로그래밍
'Back-end > Spring 기초개념' 카테고리의 다른 글
Entity에 setter를 사용하지 않는 진짜 이유 (1) | 2024.03.07 |
---|---|
JPA와 hibernate / Spring data JPA (0) | 2023.05.05 |
Instagram-clone 분석 (3) Controller (0) | 2023.05.05 |
Instagram-clone 분석 (3) DTO (0) | 2023.04.23 |
Instagram-clone 분석 (2) entity (3) | 2023.04.22 |