안녕세계

[JPA] JPA 사용시 유의사항 본문

[JPA] JPA 사용시 유의사항

Junhong Kim 2025. 4. 19. 11:20
728x90
반응형

1차 캐시 작동 조건

JPA의 1차 캐시는 엔티티를 메모리에 임시로 저장하여 불필요한 DB 쿼리를 줄이고 성능을 향상시키는 중요한 기능입니다. 1차 캐시는 EntityManager가 관리하는 영속성 컨텍스트(persistence context) 내에서 엔티티를 보관하는 저장소로, 주로 트랜잭션 범위 내에서 작동합니다. 즉, 한 트랜잭션 내에서 처음 조회한 엔티티는 두 번째부터 DB에 접근하지 않고 1차 캐시를 통해 데이터를 가져올 수 있게 됩니다. 1차 캐시가 제대로 작동하기 위해서는 몇 가지 조건이 필요합니다.

 

  1. 영속성 컨텍스트 존재
    • 1차 캐시는 영속성 컨텍스트 내부에 있으므로, 영속성 컨텍스트가 반드시 존재해야합니다.
      • 각 영속성 컨텍스트는 독립적인 1차 캐시를 가지므로, 다른 영속성 컨텍스트에서는 동일 엔티티라도 캐시를 공유하지 않습니다.
    • 1차 캐시는 보통 트랜잭션 시작 시 생성되고 트랜잭션 종료 시 소멸됩니다.
      • 1차 캐시는 트랜잭션 내에서만 유효합니다. 트랜잭션이 커밋되거나 롤백되면, 캐시는 초기화됩니다.
  2. 엔티티 상태
    • 엔티티가 영속성 컨텍스트에 의해 관리되고 있어야 1차 캐시가 작동합니다.
      • 즉, 조회하려는 엔티티는 영속(Managed) 상태여야 합니다.
  3. 동일한 엔티티 객체
    • 동일한 엔티티 객체를 조회해야만 1차 캐시를 활용할 수 있습니다.
      같은 PK 값을 가진 엔티티 객체는 동일한 객체를 참조하므로 1차 캐시가 적용됩니다.
    • 1차 캐시는 Primary Key를 기반으로 엔티티를 관리합니다. 따라서, PK가 변경되면 캐시가 무효화됩니다.
  4. 엔티티 조회 방법
    • 엔티티를 조회할 때 PK(Primary Key)를 사용해야 합니다.
      • 예를 들어, EntityManager.find(), findById() 메서드를 사용하여 조회해야 1차 캐시를 사용합니다.
      • JPA는 PK가 아닌 조건으로 조회할 경우, 1차 캐시를 먼저 확인하지 않습니다. 즉, 비 PK 조건으로 엔티티를 조회하면 항상 쿼리를 실행하고, 결과로 얻은 엔티티 ID가 이미 1차 캐시에 있다면 캐시에 있는 객체를 반환합니다.
    • 🚨 JPQL을 사용한 PK 조회는 "항상 쿼리를 실행"하므로 1차 캐시가 사용되지 않습니다.
      • JPQL 조회 쿼리는 entityManager.createQuery()를 통해 실행되기 때문에, JPQL을 사용해 PK를 조회하더라도 항상 쿼리가 실행됩니다. createQuery()는 1차 캐시를 사용하지 않고 새로운 쿼리가 실행한다는 의미입니다.
      • JPQL로 조회한 엔티티도 EntityManager에 의해 관리되므로, 조회한 엔티티가 영속성 컨텍스트에 추가됩니다. 이 경우에도 1차 캐시에 저장되어 같은 트랜잭션 내에서 캐시를 사용할 수 있습니다.
※ PK로 조회한 경우가 아니어도 데이터를 1차 캐시에 저장합니다. 
-> 다만, 1차 캐시의 키는 항상 ID(PK)이기 때문에, PK가 아닌 조건으로 조회했더라도 내부적으로는 ID 기준으로 캐싱됩니다.

 


연관관계 Optional 설정

JPA에서 연관관계 매핑 시 사용하는 optional 속성은 연관된 엔티티가 null을 허용할지 여부를 결정합니다. optional 설정은 스키마 생성시 NOT NULL 제약을 설정하지는 않습니다. 컬럼 제약을 설정하려면 @JoinColumn의 nullable=false 설정을 해야 합니다.

  • @OneToOne
    • optional = false (기본 값)
      • 외래 키가 컬럼으로 전재, nullable 설정 가능
    • 연관 엔티티가 반드시 존재해야 하므로, Hibernate는 연관 엔티티를 가져올 때 INNER JOIN을 사용합니다.
  • @ManyToOne
    • optional = true (기본 값)
      • 외래 키가 컬럼으로 전재, nullable 설정 가능
    • Hibernate는 연관 엔티티를 가져올 때 LEFT OUTER JOIN을 사용합니다.
  • @OneToMany, @ManyToMany
    • optional 설정 불가능
 @OneToMany, @ManyToMany 에서 optional 설정이 불가능합니다.

optional은 연관관계가 null이 될 수 있는가를 명시하는 것입니다. 따라서, @OneToOne, @ManyToOne 에서는 테이블 컬럼에 외래 키가 직접 존재하므로, nullable 여부를 제어할 수 있기 때문에 연관관계 주인에서만 의미가 있는 옵션입니다.

@OneToMany는 외래키를 관리하지 않은으므로, 외래키의 null 여부를 제어할 수 없기 때문에 optional 설정이 불가능합니다. @ManyToMany는 양방향 엔티티 모두 외래 키를 가지지 않고, 중간 조인 테이블을 통해 관계가 관리됩니다. 따라서, @ManyToMany에서는 외래키가 자기 테이블에 없고, 전부 조인 테이블에 존재하므로 nullable 이라는 개념 자체가 엔티티 필드에 적용되지 않습니다.

 


양방향 연관관계에서 @OneToOne 은 지연로딩? 즉시로딩?

양방향 연관관계에서 "연관관계의 주인이 아닌 엔티티"를 조회할 때는 항상 즉시로딩(EAGER)로 동작  합니다. 

 

양방향 연관관계에서 한 쪽 엔티티는 연관관계의 주인이고, 다른 쪽은 비주인입니다.
연관관계의 주인은 연관된 객체의 외래키를 관리하는 역할을 하며, 비주인은 관계의 방향을 반영하는 필드만 가집니다.

  • 연관관계의 주인은 @JoinColumn을 통해 외래키를 관리합니다.
  • 비주인은 mappedBy 속성을 사용해 반대편의 필드를 참조합니다.

양방향 연관관계에서 연관관계의 주인이 아닌 쪽 엔티티를 조회할 때 EAGER 로딩이 동작하는 이유는?

  • JPA에서 연관관계를 관리할 수 있으려면 연관 필드에는 실제 엔티티 객체, 프록시 객체, 또는 null이 들어올 수 있으며, 이 셋 중 어떤 것이든 JPA는 정상적으로 관계를 추적하고 관리할 수 있습니다.
  • JPA가 지연 로딩(Lazy Loading)을 적용하려면 프록시 객체가 필요하며, 이를 위해서는 연관관계의 주인 쪽이 외래 키 정보를 갖고 있어야 합니다. 반대로 연관관계의 주인이 아닌 비주인 쪽은 외래 키가 없기 때문에 어떤 대상을 프록시로 감싸야 하는지 알 수 없어 지연 로딩이 불가능하고, 이 때문에 JPA는 해당 연관 엔티티를 즉시 로딩(EAGER) 방식으로 가져옵니다.
    • 따라서, @OneToOne 양방향 연관관계에서 비주인에서 엔티티를 조회할 때, 프록시 객체가 아닌 실제 객체를 즉시 로딩해야 관계가 제대로 매핑됩니다. 비주인은 외래키 정보가 없어서 프록시를 만들 수 없기 때문에, JPA는 프록시 대신 진짜 객체를 즉시로딩합니다.

참고

728x90
반응형

'Server > JPA' 카테고리의 다른 글

[JPA] Soft Delete 적용하기 : @Where vs @SQLRestriction  (2) 2025.03.15
[JPA] Entity Life Cycle  (0) 2024.08.06
[JPA] 성능 최적화  (0) 2023.05.06
Comments