[Spring] JPA 기초 개념
Updated: Categories: CSJPA 기초 개념 다시 정리하기
Entity
EntityFactory
- Bean으로 등록됨
- EntityManager를 생성해준다
- Thread Safe 함
EntityManager
- 요청이 들어오면 EntityFactory에 의해 생성된다
- Thread Safe하지 않다
- Entity 객체를 영속성 컨텍스트에 보관하며 관리한다
- 커넥션 풀에서 커넥션을 가져와 사용한다
- 트랜잭션이 커밋될때에 커넥션을 사용함
PersistenceContext
- 영속성 컨텍스트
- 비휘발성으로 Entity를 저장하고 있음
- 저장된 Entity는 PK가 필수임
- 1차 캐시에서 key-value(PK-Entity)로 엔티티를 관리함
- 트랜잭션과 생명주기가 동일하다
- 트랜잭션이 시작될때 컨텍스트 생성
- 트랜잭션이 끝날때 컨텍스트 종료
- 트랜잭션을 커밋할때 Flush가 발생하며 컨텍스트 내 Entity 상태를 DB에 반영한다
- flush : 영속성 컨텍스트와 DB를 동기화 하는 작업 (쿼리를 날리는 작업)
- 엔티티 매니저에서 직접 호출
- 트랜잭션 커밋 시 자동 호출
- JPQL 쿼리 실행 전 자동 호출 (영속화 싱크 맞추기 위해)
- flush : 영속성 컨텍스트와 DB를 동기화 하는 작업 (쿼리를 날리는 작업)
Entity Lifecycle
- 비영속 (new / transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속 (managed) : 영속성 컨텍스트에 저장된 상태
- 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed) : 삭제된 상태
- 비영속 vs 준영속 : 준영속상태는 영속되었다가 해제된것이기에 무조건 PK를 가지고 있음
특징
- 1차 캐시
- 찾고자 하는 Entity가 1차캐시에 존재한다면(영속화 되어있다면) 쿼리를 날리지 않고 캐시에서 가져옴
- 없다면 쿼리날리고 1차캐시에 저장 -> 반환
- 동일성 보장
- 조회 결과 Entity가 동일한 경우, 1차 캐시에서 가져오므로 동일한 주소를 가진 객체를 보장함
- 트랜잭션을 지원하는 쓰기지연
- 1차캐시의 정보가 변경되면 쿼리문을 쓰기지연 저장소(메모리)에 저장해놓음
- flush될때 저장소의 쿼리를 한번에 날림
- 변경 감지
- 영속화된 Entity가 수정될 경우, 수정된 Entity는 1차캐시의 스냅샷에 저장됨
- flush 시점에 최초 엔티티와 스냅샷 엔티티를 비교하여 update쿼리문을 생성 후 날림
즉시/지연 로딩
- 지연 로딩 (FetchType.LAZY)
- 엔티티 조회시 연관관계의 엔티티를 프록시로 가져오는 것
- 실제 사용시 프록시가 초기화되며 쿼리가 나간다 (N+1)
- 프록시가 초기화되도 프록시 객체가 실제 엔티티로 바뀌는건 아님
- 프록시 객체는 식별자 값만 가지고 있음
- 실제 사용시까지 쿼리를 지연하며 불필요한 쿼리를 날리지 않기 위해 사용.
- 엔티티 조회시 연관관계의 엔티티를 프록시로 가져오는 것
- 즉시 로딩 (FetchType.EAGER)
- 엔티티 조회시 연관관계의 엔티티를 실제로 가져오는 것
- JPA 구현체는 N+1을 방지하기 위해 즉시 로딩시 조인을 사용한다
- 연관관계가 많다면 조인이 개많이 일어날 수 있음
- JPQL에서는 N+1 문제를 일으킴
- 엔티티 조회시 연관관계의 엔티티를 실제로 가져오는 것
N+1 문제 해결
- fetch join + distinct 사용
@EntityGraph
사용- hibernate
@BatchSize
적용 - hibernate
@Fetch(FetchMode.SUBSELECT)
적용
연관관계 매핑
- 테이블은 외래키(FK) 하나로 양방향 조인됨
- 객체는 양쪽에서 참조해줘야 함
단방향
- 외래키를 가진쪽(N)이
@ManyToOne
+@JoinColumn
양방향
- 반대쪽(1, 부모)이
@OneToMany(mappedBy="주인 아닌 테이블명")
- 외래키를 가진쪽(N, 자식)이
@ManyToOne
+@JoinColumn
/ 연관관계 주인- 부모쪽 리스트에 자식을 추가하면 자식쪽에서도 부모와 연관관계를 맺어줘야함 -> 연관관계 편의메소드
영속성 전이
- 특정 엔티티를 영속화 시킬때 연관관계의 엔티티까지 영속화시키는 기능
- 보통
@OneToMany
관계의 리스트 요소의 상태를 변경시켰을때 실제 엔티티까지 변경되도록 하려고 씀 (수정, 삭제)
- 보통
- 종류
- default : 아무것도 전이하지 않는 상태
- ALL
- PERSIST
- MERGE
- REMOVE
- REFRESH
- DETACH
- 고아객체
- 고아객체가 생성되는 경우
- 부모엔티티 전체가 삭제되어 모든 자식 엔티티 참조가 끊어짐
- 부모엔티티 리스트에서 요소를 삭제하여 해당 자식 엔티티 참조가 끊어짐
@OneToMany(orphanRemoval = true)
설정해주면 고아객체를 DB에서 자동 삭제해줌CascadeType.REMOVE
는 부모객체에게서 전파됨 -> 자식객체까지 삭제되는것orphanRemoval
은 고아객체가 된 자식객체만 삭제됨
- 고아객체가 생성되는 경우
@Transactional
- 대상 메소드에 AOP가 동작하여 트랜잭션 기능 Advice가 적용됨
- 따라서 private 메소드는 프록시 생성안됨 -> AOP 적용안됨 -> 트랜잭션 안걸림
- 영속성 컨텍스트와 생명주기가 동일하다
- 엔티티 매니저는 동일해도 트랜잭션이 다르면 영속성 컨텍스트도 다르다
옵션
readOnly
: 트랜잭션을 읽기전용으로 설정- hibernate session flush 모드를 MANUAL로 설정 (?)
- 읽기 전용이라 영속성 컨텍스트에서 스냅샷을 보관하지 않음 -> 메모리 덜씀
- flush를 생략함 -> 성능향상
isolation
: 트랜잭션 격리 수준propagation
: 트랜잭션 전파 수준rollbackFor
: 특정 예외 발생시 롤백을 명시noRollbackFor
: 특정 예외 발생시 롤백하지 않음을 명시timeout
: 트랜잭션 실행 시간을 정해놓고 시간 지나면 롤백
격리수준
@Transactional(isolation=Isolation.???)
DEFAULT
: 연결된 DB의 격리수준을 따름READ_UNCOMMITTED
: 제한없이 모든 데이터 조회READ_COMMITTED
: 커밋 완료된 데이터만 조회REPEATABLE_READ
: 트랜잭션이 시작되기 전 커밋된 데이터만 조회 (MySQL default)SERIALIZABLE
: 트랜잭션 종료시까지 공유잠금
전파수준
@Transactional(propagation=Propagation.???)
REQUIRED
(default) : 부모 트랜잭션이 있다면 부모에게 포함된다. 없다면 새로운 트랜잭션을 실행한다.REQUIRED_NEW
: 부모 트랜잭션이 있어도 포함되지 않고 독립적인 새로운 트랜잭션을 실행한다.NESTED
: 부모 트랜잭션이 있으면 중첩 트랜잭션을 생성함- 중첩 트랜잭션의 커밋은 부모 트랜잭션이 끝날때 이루어짐
- 중첩 트랜잭션에서 롤백돼도 부모에게 전파 X / 부모가 롤백되면 다 롤백
MANDATORY
: REQUIRED와 동일 + 호출 전 반드시 외부에 실행중인 트랜잭션이 존재해야한다.NEVER
: MANDATORY와 정반대. 트랜잭션 실행중엔 해당 메소드는 실행될 수 없음SUPPORTS
: 외부에서 실행중인 트랜잭션이 있다면 그 트랜잭션에 포함된다. 없다면 그냥 PASSNOT_SUPPORTED
: 외부에서 실행중인 트랜잭션이 있어도 NOT_SUPPORTED 어노테이션이 달린 메소드는 제외된다.- 참고링크 : https://deveric.tistory.com/86
준영속 상태
- 영속성 컨텍스트는 Service와 Repository의 비즈니스 계층에서만 살아있음
- 만약 Controller 계층으로 엔티티를 반환하면 해당 엔티티는 준영속 상태가 됨 -> 프록시 초기화 못함
OSIV
- Open Session In View
- 트랜잭션 시작시점을 필터부분으로 옮겨서 영속성 컨텍스트의 생존범위를 늘려버리는 방법
- 비즈니스 계층이 아닌 다른 계층에서 엔티티를 변경해도 DB에 반영되어버리는 문제 발생
- 엔티티 객체에서 수정을 막던가 DTO로 변환시켜 해결함
Spring OSIV
- 영속성 컨텍스트는 계속 살아있지만 트랜잭션은 비즈니스 계층에서만 발생
- 따라서 비즈니스 계층에서만 flush(수정)가능
- 트랜잭션이 없는 다른 계층에서는 조회만 가능