이미 컬렉션 래퍼에 관해서는 8장에서 알아 보았는데 이번 장에서 다시 언급되고 있어서 정리 내용을 그대로 다시 가져왔다.
일대다 관계와 같은 것을 객체에 표현할 때 List 혹은 Set 을 사용하게 되는데, 이를 지연로딩하게 되면 하이버네이트는 하이버네이트가 제공하는 내장 컬렉션으로 이를 제공하는데 이를 컬랙션 래퍼라고 한다. 위에서 살펴본 프록시와 동일하게 동작한다.
하지만 List 와 Set 은 약간 다르게 동작한다. 중복에 대한 처리 여부가 List 와 Set 이 다르기 때문에 발생하는 차이인데 아래 코드에서 차이를 살펴보자.
List 인 경우
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Member> members = new ArrayList<>();
@Override
@Transactional
public void run(ApplicationArguments args) {
final Team team = this.entityManager.find(Team.class, 1L);
System.out.println("className : " + team.getMembers().getClass().getName());
System.out.println(this.isLoaded(team.getMembers()));
final Member newMember = new Member();
newMember.setTeam(team);
team.getMembers().add(newMember);
System.out.println(this.isLoaded(team.getMembers()));
}
Hibernate:
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.id=?
className : org.hibernate.collection.internal.PersistentBag
false
false
Hibernate:
insert
into
member
(name, team_id)
values
(?, ?)
일단 컬랙션 래퍼의 이름이 PersistentBag 인 것을 알 수 있다. 새로운 member를 List 에 추가했지만 끝까지 members 는 초기화가 되지 않고 있다.
Set 인 경우
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Member> members = new HashSet<>();
@Override
@Transactional
public void run(ApplicationArguments args) {
final Team team = this.entityManager.find(Team.class, 1L);
System.out.println("className : " + team.getMembers().getClass().getName());
System.out.println(this.isLoaded(team.getMembers()));
final Member newMember = new Member();
newMember.setTeam(team);
team.getMembers().add(newMember);
System.out.println(this.isLoaded(team.getMembers()));
}
Hibernate:
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.id=?
className : org.hibernate.collection.internal.PersistentSet
false
Hibernate:
select
members0_.team_id as team_id3_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.name as name2_0_1_,
members0_.team_id as team_id3_0_1_
from
member members0_
where
members0_.team_id=?
true
Hibernate:
insert
into
member
(name, team_id)
values
(?, ?)
컬랙션 래퍼의 이름이 PersistentSet 이다. List 때와 사용된 컬랙션 래퍼가 일단 다르다. 그리고 새로운 member 를 add 하는 순간 초기화가 발생한다.
왜냐하면 Set 은 내부 element 의 유일성을 보장해야하는데, 이를 위해서는 기존 컬랙션에 어떤 element 들이 있는지 알아야하기 때문이다. 따라서 element 를 add 하는 행위는 컬랙션 래퍼의 초기화를 유발한다.
그리고 정상적으로 쓰기지연 처리가 되는 모습을 확인할 수 있다.
JPA 리스너
PostLoad : 엔티티가 영속성 컨텍스트에 조회된 직후, 또는 refresh를 호출한 후(2차 캐시에 저장되어 있어도 호출된다.)
PrePersist : persist() 메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출된다. 식별자 생성 전략을 사용한 경우에는 엔티티의 식별자는 존재하지 않는 상태이다. 새로운 인스턴스를 merge 할 때도 수행된다.
PreUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정하기 직전에 호출된다.
PreRemove : remove 메소드를 호출해서 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 또한 삭제 명령어로 영속성 전이가 일어날 때도 호출된다. orphanRemoval 에 대해서는 flush나 commit 시에 호출된다.
PostPersist : flush나 commit을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다. 참고로 식별자 생성 전략이 IDENTITY인 경우 식별자를 생성하기 위해 persist()를 호출하면서 데이터베이스에 해당 엔티티를 저장하므로, 이때는 persist()를 호출한 직후에 바로 PostPersist가 호출된다.
PostUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출된다.(persist 시에는 호출되지 않는다)
PostRemove : flush나 commit을 호출해서 엔티티를 데이터베이스에 삭제한 직후에 호출된다.