detached

김민지·2022년 11월 4일
0

JPA

목록 보기
17/27

detached

  • detached 상태는 제대로 표현한다면, "지속성이 보장되고 있지만 세션과 현재 분리된 상태"를 의미합니다.

Detach가 되는 여러가지 상황

Detached 상태가 될 수 있는 상황들을 그림을 통해서 다시 설명드리겠습니다.

  1. 첫번째 상황에서는 id가 1L에 해당하는 엔티티가 Persistence Context나 DB에 없기 때문에, article객체는 Transient/New 상태입니다.
  2. 두번째 상황에서는 DB에 id가 1L에 해당하는 엔티티가 이미 있는 상태이면서, 새로 생성된 객체가 Persistence Context에 의해 관리되지 않는(세션에 연관되어있지 않은) 상태이므로, article객체는 Detached 상태입니다.
  3. 세번째 상황에서는 Persistence Context에 id가 1L에 해당하는 엔티티가 이미 있는 상태이지만, 새로 생성된 객체가 Persistence Context에 의해 관리되지 않는 상태이므로, 생성된 article 객체는 Detached 상태입니다.
  4. 네번째 상황에서 Persistence Context에 id가 1L에 해당하는 엔티티가 있고, 이를 find로 찾아왓을 때에는, 이 객체가 Persistence Context에 의해 관리되는 중이므로, Managed 상태입니다.
    하지만, detach메서드로 Persistence Context(세션)과의 관계를 끊을 경우 이 article 객체는 Detached 상태가 됩니다. (Detached가 되더라도, 트랜잭션이 종료될 때, Persistence Context 내에 있던 내용은 <DB에 정상적으로 반영이 됩니다. 잠시 후에 더 자세히 살펴보겠습니다.)
  5. 마지막 상황에서는 Persistence Context로부터 id가 1L에 해당하는 엔티티를 find로 찾아왔으므로 우선 Managed 상태입니다. 하지만, Persistence Context(세션)이 종료될 때, 이 article 객체는 Persistence Context와의 연결이 끊기기 때문에, Detached 상태가 됩니다.
@Test
void DetachedArticle_WillBeInserted() {
    //given
    Article article = Article.builder().content("hello").build();

    //when, then
    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)

    1. id o, in 영컨 o -> 준1내용을 영컨엔티티에 넣고 영컨 엔티티 반환
    2. id o, in 영컨 x -> DB에 해당 ID에 대한 엔티티 요청 후 영컨에 저장 -> 준1내용을 영컨엔티티에 넣고 영컨 엔티티 반환
    3. id x -> 준1을 기반으로 새로운 엔티티 생성후 persist하고 이를 반환한다
 @Test
    @Transactional
    public void 준영속예제2(){
        //given
        String originName = "origin";
        Member member = Member.builder().name(originName).id(10l).build();//내가 2l로 설정해줌
        //when
        em.merge(member);
        em.flush();
        em.clear();
        //then
        Member findMember = em.find(Member.class, 10l);//10l으로 설정해줬지만
        //id autoincrese를 따르기때문에 10l으로 찾으면 안뜸
        Assertions.assertNotNull(findMember);

    }

참조 무결성 제약조건을 유의하기

  • 참조무결성제약조건: 외래키는 null이거나 그 키에 해당하는 대상이 존재해야함
  • 관계형디비에선 fk를 가지고 서로를 참조할 수 있어
    그런데 만약 한쪽이 삭제되면 어떻게 되는걸까? a -> b를 참조하는데 b가 삭제되면 a가 가지고 있는 fk로는 b를 참조할 수 없어
    삭제됐으니깐
    근데 이러면 외래키가 null이 아닌데 그 키에 해당하는 대상이 존재하지 않아서 참조무결성제약조건을 위배해
    이렇게 되면 롤백이 돼!

어떻게 해야할까?

  1. cascade사용
  2. a->b에서 b를 삭제하고 a객체의 b참조변수에 대해 null을 넣어주는 것
  3. 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

profile
안녕하세요!

0개의 댓글