detached
- detached 상태는 제대로 표현한다면, "지속성이 보장되고 있지만 세션과 현재 분리된 상태"를 의미합니다.
Detach가 되는 여러가지 상황
Detached 상태가 될 수 있는 상황들을 그림을 통해서 다시 설명드리겠습니다.
- 첫번째 상황에서는 id가 1L에 해당하는 엔티티가 Persistence Context나 DB에 없기 때문에, article객체는 Transient/New 상태입니다.
- 두번째 상황에서는 DB에 id가 1L에 해당하는 엔티티가 이미 있는 상태이면서, 새로 생성된 객체가 Persistence Context에 의해 관리되지 않는(세션에 연관되어있지 않은) 상태이므로, article객체는 Detached 상태입니다.
- 세번째 상황에서는 Persistence Context에 id가 1L에 해당하는 엔티티가 이미 있는 상태이지만, 새로 생성된 객체가 Persistence Context에 의해 관리되지 않는 상태이므로, 생성된 article 객체는 Detached 상태입니다.
- 네번째 상황에서 Persistence Context에 id가 1L에 해당하는 엔티티가 있고, 이를 find로 찾아왓을 때에는, 이 객체가 Persistence Context에 의해 관리되는 중이므로, Managed 상태입니다.
하지만, detach메서드로 Persistence Context(세션)과의 관계를 끊을 경우 이 article 객체는 Detached 상태가 됩니다. (Detached가 되더라도, 트랜잭션이 종료될 때, Persistence Context 내에 있던 내용은 <DB에 정상적으로 반영이 됩니다. 잠시 후에 더 자세히 살펴보겠습니다.)
- 마지막 상황에서는 Persistence Context로부터 id가 1L에 해당하는 엔티티를 find로 찾아왔으므로 우선 Managed 상태입니다. 하지만, Persistence Context(세션)이 종료될 때, 이 article 객체는 Persistence Context와의 연결이 끊기기 때문에, Detached 상태가 됩니다.
@Test
void DetachedArticle_WillBeInserted() {
Article article = Article.builder().content("hello").build();
em.persist(article);
em.detach(article);
Article article2 = em.find(Article.class, 1L);
Assertions.assertEquals("hello", article2.getContent());
em.flush();
em.clear();
Article article3 = em.find(Article.class, 1L);
Assertions.assertEquals("hello", article3.getContent());
}
- 이를 확인하기 위해, article를 persist 한 후, 이를 detach 하더라도, 여전히 PersistenceContext상에서는 id가 1L에 해당하는 엔티티를 찾아올 수 있습니다.
- 더욱이, 이를 db상에 저장하고 PersistenceContext를 초기화하더라도, db로부터 id가 1L에 해당하는 엔티티를 찾아올 수 있습니다.
merge
- merge의 목적은 detached 상태의 인스턴스를 이용해, Persistence Context나 DB의 엔티티를 업데이트 하는 것입니다.
merge 예시
-
updateMember메서드에서는 em.merge를 수행한다
-
간단하게 생각하면, 여기서 merge가 호출되었을 때, member가 가지고 있는 내용을 기반으로 Persistence Context 및 DB에 저장된다고 이해할 수 있습니다. 하지만 실제로는 이 전달된 객체가 id를 가지고 있는지에 따라서, 또 실제로 그 id에 해당하는 엔티티가 있었는지에 따라서 로직이 달라집니다.
em.merge(준1)
- id o, in 영컨 o -> 준1내용을 영컨엔티티에 넣고 영컨 엔티티 반환
- id o, in 영컨 x -> DB에 해당 ID에 대한 엔티티 요청 후 영컨에 저장 -> 준1내용을 영컨엔티티에 넣고 영컨 엔티티 반환
- id x -> 준1을 기반으로 새로운 엔티티 생성후 persist하고 이를 반환한다
@Test
@Transactional
public void 준영속예제2(){
String originName = "origin";
Member member = Member.builder().name(originName).id(10l).build();
em.merge(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, 10l);
Assertions.assertNotNull(findMember);
}
참조 무결성 제약조건을 유의하기
- 참조무결성제약조건: 외래키는 null이거나 그 키에 해당하는 대상이 존재해야함
- 관계형디비에선 fk를 가지고 서로를 참조할 수 있어
그런데 만약 한쪽이 삭제되면 어떻게 되는걸까? a -> b를 참조하는데 b가 삭제되면 a가 가지고 있는 fk로는 b를 참조할 수 없어
삭제됐으니깐
근데 이러면 외래키가 null이 아닌데 그 키에 해당하는 대상이 존재하지 않아서 참조무결성제약조건을 위배해
이렇게 되면 롤백이 돼!
어떻게 해야할까?
- cascade사용
- a->b에서 b를 삭제하고 a객체의 b참조변수에 대해 null을 넣어주는 것
- a entity내에 b와의 연결관계를 제거하는 메서드를 정의한다
정리
준영속 엔티티
- 한번이라도 영속화된적이 있는 엔티티(하지만 현재는 아닌)를 지칭
merge의 목적
- save(
entity1
); 를 호출하면 entity1
이 최초등록된 엔티티라면 persist가 호출되고, 준영속 엔티티라면 merge가 수행된다.
- 준영속엔티티를 이용해서 영속엔티티/db엔티티 내용을 update하는것
merge와 persist의 차이
persist:
- 데이터베이스에 새 레지스터 삽입
- 객체를 엔티티 관리자에 연결합니다.
merge:
- 동일한 ID를 가진 연결된 개체를 찾아 업데이트합니다.
- 존재하는 경우 이미 첨부된 객체를 업데이트하고 반환합니다.
- 존재하지 않는 경우 데이터베이스에 새 레지스터를 삽입합니다.
merge의 동작방식
em.merge(준영속객체
)
id:o, in영컨 o -> 준영속객체
내용을 영컨에있는 엔티티에 넣은 후에 그 엔티티 반환
id:o, 영컨x -> 디비에 해당 id값에 대한 엔티티 내놓으라고 요청헤서 얻어낸 뒤에 그 엔티티에 준영속객체
내용을 넣고 반환
id:x -> 준영속객체
에 대한 내용을 바탕으로 엔티티를 만들고 persist한다음에 반환한다
출처
https://tech.junhabaek.net/hibernate-jpa-entitymanager-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EB%8A%A5-%EC%A0%95%EB%A6%AC-3d0d9ff439a2#0f85