개발자 포포

[Hibernate] Collection 타입은 PersistentBag 타입으로 래핑된다.

popo.se 2024. 9. 17. 15:14

이 글은 과거에 운영하던 블로그에서 옮겨온 글 입니다. (2022.01.15. 작성됨)


요약

하이버네이트에서는 컬렉션 타입을 org.hibernate.collection.internal.PersistentBag 인스턴스로 래핑하여 사용합니다.

이 때문에 Collection 타입에서 orphanRemoval 옵션 사용시 주의 해야합니다.

이와 관련 하여 실제 문제를 격은 사레는 다음 포스팅에서 다뤄져 있습니다.

2024.09.17 - [개발자 포포] - [Hibernate] orphanRemoval 옵션 사용시 Collection 참조를 변경하지 말자

 


JPA 관련 공부를 하던 중, 컬렉션 타입은 org.hibernate.collection.internal.PersistentBag 인스턴스로 래핑된다는 놀라운 사실을 알게 되었습니다.

 

테스트 해보기 위해 간단히 1:N 관계를 가지는 Team과 Member를 정의하고 다음과 같이 연관관계를 맺어 주었습니다.

 

@Entity
public class Team {

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

  private String name;

  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<>();

  protected Team() {
  }

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

  public void addMember(Member member){
    members.add(member);
    member.assignTeam(this);
  }
  ...
}

@Entity
public class Member {

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

  private String name;

// @JoinColumn(name = "member")@ManyToOne
  private Team team;

  protected Member() {
  }

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

  public void assignTeam(Team team){
    this.team=team;
  }
}

 

다음과 같이 테스트 코드를 작성하여 members 컬렉션의 클래스 타입을 확인해 보았습니다.

  @Test
  void test() {
    Team team = new Team("TeamA");

    System.out.println("before persist -- : " + team.getMembers().getClass());
    entityManager.persist(team);

    System.out.println("after persist -- : " + team.getMembers().getClass());
    entityManager.flush();// 영속성 컨텍스트에 있는 것을 DB에 반영. 영속성 컨텍스트를 비우진 않음.
    entityManager.clear();// 영속성 컨텍스트 비움

    Team findTeam = entityManager.find(Team.class, team.getId());// clear를 호출했기 때문에 db에서 다시 찾음
    System.out.println("find team - member -- : " + findTeam.getMembers().getClass());
  }

 

[결과]

영속성 상태 전에는 ArrayList 타입이었다가 영속성 상태가 되고 난 후 PersistentBag로 변경되는 것을 확인 할 수 있습니다.

before persist-- : class java.util.ArrayList
Hibernate:
    insert
    into
        team
        (id, name)
    values
        (null, ?)
after persist-- : class org.hibernate.collection.internal.PersistentBag
Hibernate:
    select
        team0_.id as id1_1_0_,
        team0_.name as name2_1_0_
    from
        team team0_
    where
        team0_.id=?
find team - member-- : class org.hibernate.collection.internal.PersistentBag

 

하이버네이트는 컬렉션으로 참조하고 있는 대상을 추적하고 관리하기 위해 내부적으로 PersistentBag 타입 객체로 실제 인스턴스를 래핑하여 사용하게 됩니다.

 

PersistentBag 의 내부 구현은 다음과 같습니다. 컬렉션을 인스턴스 변수로 두고 생성자에서 이를 주입받는 것을 확인할 수 있습니다.

public class PersistentBag extends AbstractPersistentCollection implements List {

  protected List bag;
  ...

  public PersistentBag(SharedSessionContractImplementor session, Collection coll) {
     super( session );
     providedCollection = coll;
     if ( coll instanceof List ) {
        bag = (List) coll;
     }
     else {
        bag = new ArrayList( coll );
     }
     setInitialized();
     setDirectlyAccessible( true );
  }

}

 

[참고]

책 - 자바 ORM 표준 JPA 프로그래밍 (저자 - 김영한)