[Spring] JPA 기초 개념

Updated: Categories:

JPA 기초 개념 다시 정리하기

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 쿼리 실행 전 자동 호출 (영속화 싱크 맞추기 위해)

Entity Lifecycle

  • 비영속 (new / transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속 (managed) : 영속성 컨텍스트에 저장된 상태
  • 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제 (removed) : 삭제된 상태
  • 비영속 vs 준영속 : 준영속상태는 영속되었다가 해제된것이기에 무조건 PK를 가지고 있음

특징

Untitled Diagram drawio (12)

  • 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
  • 고아객체
    • 고아객체가 생성되는 경우
      1. 부모엔티티 전체가 삭제되어 모든 자식 엔티티 참조가 끊어짐
      2. 부모엔티티 리스트에서 요소를 삭제하여 해당 자식 엔티티 참조가 끊어짐
    • @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 : 외부에서 실행중인 트랜잭션이 있다면 그 트랜잭션에 포함된다. 없다면 그냥 PASS
  • NOT_SUPPORTED : 외부에서 실행중인 트랜잭션이 있어도 NOT_SUPPORTED 어노테이션이 달린 메소드는 제외된다.
  • 참고링크 : https://deveric.tistory.com/86


준영속 상태

image

  • 영속성 컨텍스트는 Service와 Repository의 비즈니스 계층에서만 살아있음
  • 만약 Controller 계층으로 엔티티를 반환하면 해당 엔티티는 준영속 상태가 됨 -> 프록시 초기화 못함

OSIV

image

  • Open Session In View
  • 트랜잭션 시작시점을 필터부분으로 옮겨서 영속성 컨텍스트의 생존범위를 늘려버리는 방법
  • 비즈니스 계층이 아닌 다른 계층에서 엔티티를 변경해도 DB에 반영되어버리는 문제 발생
    • 엔티티 객체에서 수정을 막던가 DTO로 변환시켜 해결함

Spring OSIV

image

  • 영속성 컨텍스트는 계속 살아있지만 트랜잭션은 비즈니스 계층에서만 발생
    • 따라서 비즈니스 계층에서만 flush(수정)가능
    • 트랜잭션이 없는 다른 계층에서는 조회만 가능