if (!isNewGroup) {
group.groupAttachFiles = groupAttachFileRepository.findByGroup(group.id)
.toMutableList()
}
// Hibernate가 관리하는 컬렉션(PersistentBag)을 일반 List로 덮어씌워서 연결이 끊어짐
// DB의 값을 미리 가져옴
val groupAttachFiles = groupAttachFileRepository.findByGroup(group.id)
// 수정 시, 항상 DB의 데이터를 반영하도록 한다.
val isNewGroup = group.id == 0L
if (!isNewGroup) {
group.groupAttachFiles.clear() // 기존 리스트를 비움 (참조 유지) (delete-orphan 동작)
groupAttachFiles.forEach {
group.groupnAttachFiles.add(it) // 가져온 파일들을 채워 넣음
}
}
영속성 컨텍스트와 컬렉션 래퍼 (Persistent Collection)
핵심: Hibernate가 엔티티의 List, Set을 어떻게 PersistentBag, PersistentSet으로 감싸는지(Proxy) 이해해야 함
- 포인트: "왜 내 리스트의 클래스 타입을 찍어보면 java.util.ArrayList가 아니라 org.hibernate.collection...이 나올까?"
고아 객체 제거 (Orphan Removal) vs Cascade.REMOVE
핵심: orphanRemoval = true 옵션의 정확한 동작 원리.
- 포인트 : list.remove(entity)를 했을 때 DB에서 DELETE 쿼리가 나가는 원리. 왜 컬렉션을 통째로 교체하면(=) Hibernate가 이를 "모든 자식을 고아로 만들었다"고 오해하거나 에러를 뱉는지.
변경 감지 (Dirty Checking)와 Merge
핵심: save()를 호출하지 않아도 트랜잭션이 끝날 때 UPDATE 쿼리가 나가는 원리.
- 포인트: JPA에서 데이터를 수정할 때 왜 setter만 호출하고 save를 부르지 않아도 되는지,
그리고 컬렉션 내부의 변경은 어떻게 감지되는지.
연관 관계 편의 메서드 (Helper Method)
핵심: 양방향 연관 관계(부모 ↔ 자식)에서 실수하기 쉬운 부분을 방지하는 패턴.
- 포인트: setParent(this)와 list.add(child)를 묶어서 하나의 메서드로 관리하는 방법 (예: campaign.addFile(file)).