본문 바로가기

JPA

[JPA] 💼 PersistentBag

💼 PersistentBag

PersistentBag은 Hibernate에서 제공하는 컬렉션 타입 중 하나로, @OneToMany 또는 @ManyToMany 관계에서 사용되는 List 타입의 컬렉션을 관리하기 위한 구현체이다. 이 컬렉션은 지연 로딩(lazy loading) 변경 감지 기능(dirty checking)을 지원하며, JPA 엔티티에서 List 타입의 필드를 사용할 때 Hibernate가 자동으로 변환하는 컬렉션 타입이다.


🤔 PersistentBag이 언제 사용될까?

✅ @OneToMany 관계에서 List를 사용할 때

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    private String name;

    @OneToMany(mappedBy = "member")
    private List<Friend> friends; // Hibernate가 PersistentBag으로 변환
}
  • 위와 같이 List 타입으로 연관 관계를 맺으면 Hibernate는 내부적으로 PersistentBag을 사용한다.
  • 위 friends 필드는 실제로 List<Friend> 타입이지만, Hibernate가 내부적으로 PersistentBag으로 감싸서 동작하게 된다.

🔍 PersistentBag의 특징

1️⃣ 초기에는 프록시 상태 (Lazy Loading)

Member member = memberRepository.findById(1L).get();
System.out.println("조회 완료");  // 이 시점에서는 friends 조회 X
System.out.println(member.getFriends().size());  // 여기서 쿼리 실행됨
  • PersistentBag은 기본적으로 지연 로딩(Lazy Loading)을 지원한다.
  • 즉, friends 필드에 접근하기 전까지 쿼리가 실행되지 않는다.
  • 💡 member.getFriends()를 호출하기 전까지는 실제 데이터가 로드되지 않는다.

2️⃣ Hibernate가 변경을 감지 (Dirty Checking)

Member member = memberRepository.findById(1L).get();
member.getFriends().add(new Friend("New Friend")); // 자동으로 추가됨
  • PersistentBag은 변경 감지 기능이 있어, 데이터가 추가되거나 삭제될 때 Hibernate가 자동으로 감지하여 UPDATE 또는 DELETE 쿼리를 실행한다.
  • 💡 add() 메서드를 호출하면 Hibernate가 변경을 감지하여 INSERT 쿼리를 실행합니다.

3️⃣ Hibernate가 직접 만든 컬렉션 타입 (일반 ArrayList 아님!)

Member member = memberRepository.findById(1L).get();
System.out.println(member.getFriends().getClass()); 
// 출력 결과: class org.hibernate.collection.internal.PersistentBag

 

  • PersistentBag은 일반적인 ArrayList가 아니라 Hibernate가 제공하는 컬렉션 타입이다.
  • 따라서 instanceof를 사용하면 다음과 같은 결과를 확인할 수 있다.
  • 💡ArrayList가 아니라 PersistentBag이 출력

4️⃣ Java의 equals()와 hashCode() 비교 시 주의

Member member = memberRepository.findById(1L).get();
List<Friend> friendList = new ArrayList<>(member.getFriends());

System.out.println(member.getFriends().equals(friendList)); // false가 나올 수도 있음!

 

  • PersistentBag은 내부적으로 동등성 비교를 다르게 처리하기 때문에, equals()나 hashCode()를 활용할 때 주의해야 한다.
  • 일반적인 ArrayList와 비교하면 다르게 동작할 수 있다.
  • 💡 PersistentBag과 ArrayList는 같아 보여도 다르게 처리될 수 있음.

⚠️  초기화 전에 size( ) 호출하면 예외 발생 가능

Member member = memberRepository.findById(1L).get();
em.close(); // 영속성 컨텍스트 종료

System.out.println(member.getFriends().size()); // 예외 발생 (LazyInitializationException)
  • Hibernate의 PersistentBag은 초기화되지 않은 상태에서 사용하면 LazyInitializationException이 발생할 수 있다.
  • PersistentSet, PersistentMap도 동일
📖 Hibernate의 컬렉션 타입별 내부 구현체
컬렉션 타입 Hibernate 내부 구현체 특징
List PersistentBag (기본값) 중복 허용, 순서 보장
Set PersistentSet 중복 허용 X
Map PersistentMap Key-Value 구조
SortedSet PersistentSortedSet 정렬된 Set
SortedMap PersistentSortedMap 정렬된 Map
  • PersistentBag은 List 전용이고, Set과 Map은 PersistentSet, PersistentMap을 사용해서 따로 관리됨!