💼 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을 사용해서 따로 관리됨!
'JPA' 카테고리의 다른 글
[JPA] ID 생성 전략 (0) | 2025.03.30 |
---|---|
[Spring Data JPA] 나머지 기능들 (0) | 2025.01.19 |
[Spring Data JPA] 구현체 분석 (0) | 2025.01.18 |
[Spring Data JPA] WEB 확장 기능, 페이지 1처리 코드 구현 (0) | 2025.01.18 |
[Spring Data JPA] Auditing (0) | 2025.01.18 |