DB 접근/JPA ( Java Persistence API )

Proxy

JABHACK 2025. 1. 14. 12:08

Entity 조회

📌 em.getReference()는 JPA의 EntityManager에서 제공하는 메서드로 특정 엔티티의 프록시 객체를 반환한다. 지연 로딩(Lazy Loading)을 활용해 데이터베이스 조회를 미루고 실제로 엔티티의 속성에 접근할 때만 데이터베이스를 조회하도록 한다.

 

@Entity
@Table(name = "tutor")
public class Tutor {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "company_id")
    private Company company;

    public Tutor() {
    }

    public Tutor(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
    
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }
}
// 1. tutor의 company를 함께 조회하는 경우
Tutor findTutor = em.find(Tutor.class, 1L);

String tutorName = findTutor.getName();
Company tutorCompany = findTutor.getCompany();

System.out.println("tutorName = " + tutorName);
System.out.println("tutorCompany.getName() = " + tutorCompany.getName());
// 2. tutor만 조회하는 경우
Tutor findTutor = em.find(Tutor.class, 1L);

String tutorName = findTutor.getName();

System.out.println("tutorName = " + tutorName);
  1. Tutor를 조회할 때 Company 를 함께 조회
    • Company를 매번 함께 조회하는것은 낭비이다.
  2. Tutor만 조회
    • Company 조회를 위해 추가적인 조회 SQL이 실행되어야 한다.
  • 이때 프록시를 사용하여 효율적으로 관리할 수 있다.

 

 

Proxy

📌 JPA에서 엔티티 객체의 지연 로딩(Lazy Loading)을 지원하기 위해 사용하는 대리 객체로 실제 엔티티 객체를 생성하거나 데이터베이스에서 값을 읽어오지 않고도 엔티티의 참조를 사용할 수 있다.

  • 대리자 또는 중간 대리 객체를 의미

  • 데이터베이스 조회를 지연하는 가짜(Proxy) 객체를 조회한다.
    • 실제 Entity와 == 비교 실패, instanceof 사용
  • target : 진짜 객체의 참조를 보관한다.

 

Proxy 객체 초기화

  1. em.getReference() : 프록시 객체 조회
  2. getName() : 프록시 객체의 getName() 호출
  3. JPA가 영속성 컨텍스트에 target 초기화 요청
  4. 실제 DB 조회
  5. Entity 생성
  6. target의 getName() 호출

 

Proxy 특징

  • 최초로 사용(실제 Entity에 접근)할 때 한 번만 초기화된다.
  • 프록시 객체를 통해 실제 Entity에 접근할 수 있다.
  • em.getReference() 호출 시 영속성 컨텍스트에 Entity가 존재하면 실제 Entity가 반환된다.
  • 준영속 상태에서 프록시를 초기화하면 LazyInitializationException 예외가 발생한다.
Tutor proxyTutor = em.getReference(Tutor.class, tutor.getId());
System.out.println("proxyTutor.getClass() = " + proxyTutor.getClass());

// 준영속 상태
em.detach(proxyTutor);

proxyTutor.getName();

 

실행결과

  • detach() : 영속성 컨텍스트가 관리하지 않는다.
  • 영속성 컨텍스트를 통해 도움을 받아야만 실제 Entity에 접근이 가능하다.
  • 실제 JPA 개발에서 가장 많이 마주치는 Exception

 

영속성 컨텍스트 (Persistence Context)

영속성 컨텍스트는 JPA(Java Persistence API)에서 엔티티 객체를 **영구 저장소(데이터베이스)**에 저장하거나 관리하는 중간 작업 영역을 의미합니다. 쉽게 말해, 엔티티 객체를 관리하는 JPA의 메모리 공간입니다.


영속성 컨텍스트의 주요 특징

  1. 엔티티 관리:
    • 영속성 컨텍스트는 엔티티 객체를 영속성 상태로 관리합니다.
    • 관리 중인 엔티티는 변경 사항이 자동으로 데이터베이스에 반영됩니다.
  2. 1차 캐시:
    • 영속성 컨텍스트는 엔티티를 1차 캐시에 저장하여, 동일한 엔티티를 데이터베이스에서 다시 조회하지 않도록 최적화합니다.
  3. 엔티티 동일성 보장:
    • 동일한 영속성 컨텍스트 내에서는 동일한 엔티티 객체를 공유(동일성 보장)합니다.
  4. 변경 감지:
    • 영속성 컨텍스트는 관리 중인 엔티티의 변경 사항을 감지하여 데이터베이스에 자동으로 반영합니다.
  5. 쓰기 지연:
    • 트랜잭션 커밋 시점에 변경 사항을 한꺼번에 데이터베이스에 반영하여 성능을 최적화합니다.
  6. 지연 로딩:
    • 필요한 시점에만 데이터베이스에서 데이터를 로드하여 효율적으로 자원을 사용합니다.

영속성 컨텍스트의 상태

  1. 비영속 (Transient):
    • 영속성 컨텍스트에서 관리되지 않는 상태.
    • 데이터베이스와 전혀 관련이 없는 상태.
    User user = new User(); // 비영속 상태
    user.setName("John");
    
  2. 영속 (Persistent):
    • 영속성 컨텍스트에 의해 관리되는 상태.
    • 데이터베이스와 연동되며 변경 사항이 자동으로 반영됩니다.
    User user = new User();
    user.setName("John");
    entityManager.persist(user); // 영속 상태
    
  3. 준영속 (Detached):
    • 영속성 컨텍스트에서 관리되지 않지만, 이전에 영속 상태였던 엔티티.
    • 데이터베이스와 연동되지 않습니다.
    entityManager.detach(user); // 준영속 상태
    
  4. 삭제 (Removed):
    • 삭제 요청이 되어 데이터베이스에서 삭제될 예정인 상태.
    • 트랜잭션이 커밋되면 데이터베이스에서 삭제됩니다.
    entityManager.remove(user); // 삭제 상태
    

영속성 컨텍스트의 주요 기능

1. 1차 캐시

  • 영속성 컨텍스트는 엔티티를 1차 캐시에 저장합니다.
  • 동일한 엔티티를 반복적으로 조회할 경우, 데이터베이스 대신 1차 캐시에서 데이터를 가져옵니다.
User user1 = entityManager.find(User.class, 1L); // DB 조회
User user2 = entityManager.find(User.class, 1L); // 1차 캐시에서 조회

2. 엔티티 동일성 보장

  • 동일한 영속성 컨텍스트에서는 동일한 엔티티 객체를 반환합니다.
User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);

System.out.println(user1 == user2); // true (같은 객체)

3. 변경 감지 (Dirty Checking)

  • 엔티티의 필드 값이 변경되면 영속성 컨텍스트가 이를 감지하고, 트랜잭션 커밋 시점에 데이터베이스에 반영합니다.
User user = entityManager.find(User.class, 1L);
user.setName("Updated Name"); // 변경 감지

entityManager.flush(); // 변경 사항 반영

4. 쓰기 지연 (Write-Behind)

  • 데이터베이스에 변경 사항을 즉시 반영하지 않고, 트랜잭션 커밋 시점에 한꺼번에 반영합니다.
entityManager.persist(user1);
entityManager.persist(user2);

// SQL 실행은 트랜잭션 커밋 시점에 발생
transaction.commit();

5. 지연 로딩 (Lazy Loading)

  • 연관된 엔티티를 실제로 사용할 때까지 데이터베이스에서 로드하지 않습니다.
User user = entityManager.find(User.class, 1L);
List<Order> orders = user.getOrders(); // 이 시점에 DB에서 로드

영속성 컨텍스트의 동작 예시

엔티티 상태 전환

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
}

// 비영속 상태
User user = new User();
user.setName("John");

// 영속 상태
entityManager.persist(user);

// 준영속 상태
entityManager.detach(user);

// 삭제 상태
entityManager.remove(user);

트랜잭션과 영속성 컨텍스트

  1. 영속성 컨텍스트는 트랜잭션 범위 내에서 관리됩니다.
    • 트랜잭션이 종료되면 영속성 컨텍스트도 종료됩니다.
  2. 트랜잭션이 커밋되면 데이터베이스와 동기화됩니다.
    • 엔티티의 변경 사항이 데이터베이스에 반영됩니다.

영속성 컨텍스트의 장점

  1. 성능 최적화:
    • 1차 캐시와 쓰기 지연을 통해 데이터베이스 접근 횟수를 줄임.
  2. 변경 감지:
    • 엔티티의 변경 사항을 자동으로 반영하여 코드 단순화.
  3. 지연 로딩:
    • 필요한 시점에만 데이터를 로드하여 리소스 사용을 최소화.
  4. 엔티티 동일성 보장:
    • 동일한 트랜잭션 내에서 같은 데이터를 일관되게 처리 가능.

영속성 컨텍스트를 활용한 주요 작업 흐름

  1. 엔티티 생성 및 영속화:
    • 새 엔티티 객체를 생성하고, 영속성 컨텍스트에 추가.
  2. 엔티티 조회:
    • 데이터베이스에서 데이터를 가져와 영속성 컨텍스트에 저장.
  3. 변경 사항 감지:
    • 엔티티의 필드 값이 변경되면 자동으로 감지하여 반영.
  4. 엔티티 삭제:
    • 영속성 컨텍스트에서 엔티티를 제거하고, 데이터베이스에서도 삭제.

정리

영속성 컨텍스트는 JPA가 엔티티 객체를 관리하고, 데이터베이스와의 상호작용을 효율적으로 처리하기 위해 사용하는 핵심 메커니즘입니다.

  • 핵심 기능: 1차 캐시, 변경 감지, 쓰기 지연, 지연 로딩.
  • 장점: 성능 최적화, 코드 단순화, 데이터 일관성 보장.

쉽게 말해, 영속성 컨텍스트는 JPA가 엔티티 객체를 "관리"하고 "동기화"하는 공간입니다!

 

'DB 접근 > JPA ( Java Persistence API )' 카테고리의 다른 글

JPA와 Transaction  (0) 2025.01.17
지연로딩, 즉시로딩  (0) 2025.01.15
상속관계 매핑  (0) 2025.01.13
연관관계  (0) 2025.01.12
[JPA] Spring Data JPA  (0) 2025.01.07