JPA 내부 동작

최형안·2025년 4월 11일

JPA란?

  • JPA(Java Persistence API)는 자바 객체를 마치 자바 컬렉션처럼 다룰 수 있도록 도와주는 ORM(Object-Relational Mapping) 기술
  • SQL 중심의 개발을 객체 중심으로 전환

엔티티 매니저와 영속성 컨텍스트

EntityManagerFactory (EMF)

  • 웹 애플리케이션은 클라이언트의 요청마다 EntityManagerFactory로부터 EntityManager를 생성한다.

EntityManager (EM)

  • EntityManager는 내부적으로 커넥션 풀에서 커넥션을 가져와 DB와 통신한다.
  • 영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로, EntityManager와 생명주기를 함께 한다.

엔티티 생명주기

상태설명
비영속단순 객체 상태, 아직 영속성 컨텍스트에 저장되지 않음
영속em.persist(entity) 호출 시, 영속성 컨텍스트에 저장됨 (DB에는 아직 저장되지 않음)
준영속em.detach(entity) 호출 시, 영속성 컨텍스트에서 분리됨
삭제em.remove(entity) 호출 시, 해당 객체는 DB에서도 삭제됨

영속성 컨텍스트의 장점

1. 1차 캐시 = 영속성 컨텍스트 = EntityManager PK + Entity + 스냅샷

  • 같은 트랜잭션 내에서 조회 시, 먼저 영속성 컨텍스트(1차 캐시)에서 검색하고, 없을 경우 DB에서 조회함
  • 한 트랜잭션 안에서의 캐시이므로 성능에 많은 영향은 없음

2. 동일성 보장

  • 같은 트랜잭션 내에서 PK가 같은 객체는 동일 객체로 관리된다 (== 비교도 true)

3. 트랜잭션을 지원하는 쓰기 지연

  • em.persist() 호출 시, 즉시 insert되지 않고 SQL 저장소에 저장됨
  • tx.commit() 시점에 flush 되어 실제 DB에 insert 수행

4. 변경 감지 (Dirty Checking)

  • em.find()로 조회한 객체를 변경하면, 커밋 시 스냅샷과 비교하여 변경된 필드만 update 쿼리 생성
  • em.persist를 다시 호출할 필요 없음
    → JPA는 DB 테이블을 컬렉션처럼 다루는 기술
    → 컬렉션에서 객체 뽑아서 값을 바꿨는데 다시 집어넣지는 않음

플러시(Flush)

  • 영속성 컨텍스트의 변경 내용을 DB에 반영하는 작업
  • 호출 시점:
    • tx.commit()
    • JPQL 실행 전
    • em.flush() 수동 호출
  • flush는 영속성 컨텍스트를 비우지 않음

준영속 상태

  • em.detach(entity) : 특정 엔티티만 분리
  • em.clear() : 전체 영속성 컨텍스트 비움
  • em.close() : 종료 (더 이상 작업 불가)

엔티티 매핑 관련

@Entity

  • JPA가 관리하는 객체로 등록하기 위한 필수 어노테이션

키 생성 전략

전략설명
IDENTITYDB가 키 생성, persist() 시 insert 쿼리 실행 후 PK 반환 → 쓰기 지연 불가
SEQUENCEDB에서 시퀀스 값만 조회 후, 쓰기 지연 가능 → 성능상 이점은 그닥

연관관계 매핑

  • 객체는 참조로 양방향 참조를 서로 1개씩 2개 가지고 있고
  • 테이블은 FK 한 개의 참조만 가지므로 → DB 테이블과 구조를 맞추기 위해 FK를 관리하는 연관관계 주인이 필요함

연관관계 주인

  • @JoinColumn은 FK를 둘 테이블과 컬럼 이름을 지정함
  • mappedBy 로 지정된 반대편이 주인 아님 (읽기 전용)
  • 주인을 설정하지 않으면 주인이 아닌 쪽도 중간 테이블을 만들어 FK를 넣어버릴 수 있음

@JoinColumn

  • FK가 저장될 컬럼 이름 지정
  • referencedColumnName: 참조 대상의 PK (또는 다른 컬럼)

JPA 프록시와 지연 로딩

프록시 (Proxy)

  • em.getReference() : 데이터베이스 조회를 미루는 가짜 객체(프록시)
  • 실제 엔티티를 상속하고 내부에 진짜 Entity를 참조할 target 필드를 가짐
  • 실제 데이터를 접근(getter 호출)할 때 DB에서 데이터를 조회해 초기화 (지연로딩)
  • 실제 객체와 다르므로 == 대신 instanceof로 비교 필요
  • 초기화는 영속성 컨텍스트를 통해 발생하므로, 준영속 상태에서 프록시 초기화 시 예외 발생

지연 로딩 (LAZY) vs 즉시 로딩 (EAGER)

  • LAZY: 연관관계 매핑 대상은 프록시로 가져옴
  • EAGER: 연관 객체까지 함께 즉시 로딩 → 복잡한 테이블에서 예측할 수 없는 JOIN 발생
  • 즉시로딩은 N+1 문제를 유발할 수 있음
  • 해결 방법: Fetch Join / Entity Graph / BatchSize 사용
  • @ManyToOne, @OneToOne은 기본이 EAGER → 반드시 LAZY로 설정 권장

영속성 전이 (Cascade)

  • 부모 엔티티를 저장/삭제할 때, 자식 엔티티도 함께 처리되도록 설정

사용 조건

  • 부모가 자식을 완전히 관리할 때 (단일 소유, 생명주기 일치)
  • 여러 부모가 하나의 자식을 관리하는 경우 절대 사용 금지

고아 객체 제거 (orphanRemoval)

  • 부모와의 연관관계가 끊어진 자식 엔티티를 자동으로 삭제함

조건

  • 자식 엔티티가 특정 부모에 종속되어야 함
  • 컬렉션에서 제거 시 DB에서도 삭제됨

0개의 댓글