ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring JPA] 객체의 상태에 대해서 알아보자 (Transient, Persistent, Detached)
    백엔드/Spring 2021. 1. 12. 23:47

     

    스프링 프레임워크

     

     

    🚀  들어가며...

    • 과거 JPA를 통해서 블로그를 개발해보겠다며, 열심히 카카오톡 oAuth2.0 로그인 기능을 붙이며 개발했던 기억이 있다.
    • 그러면서 저장한 객체를 재사용하였는데, 재사용과 동시에 update가 되는 경우가 있었다.
    • 당시 JPA에 대한 이해가 부족했기에, 이제라도 남기는 글
    • (현재 아직 많이 부족하지만, 이제는 많이 달라진 것 같다)

     

     

    📑 내용

    • 우선 설명부터 해야겠다.

     

    JPA의 객체 상태(Object status)의 경우 세가지가 존재한다.

     

    1. Transient (과도기)

    • 말 그대로 투명한 상태를 뜻한다.
    • 알기 쉽게 설명하자면, 객체 저장 이전의 상태를 뜻하여, 데이터베이스나 EM(entityManager)가 관리하지 않는 상태이다.
    • ID 값이 존재하지 않으며, 하이버네이트가 save()를 통해서 Persistent 상태로 변경한다.

     

    2. Persistent (영속)

    • 쉽게 설명하자면, 객체를 저장한 상태라고 쉽게 이해할 수 있다. (그건 또 아닌 것 같지만..)
    • 하이버네이트의 save()를 통해서 ID를 할당하며, 세션의 범위는 개발자가 정의할 수 있다.
    • 이후의 트랜젝션이 끝날때까지, 모든 변경 사항을 감지하고 동기화한다.

     

    3. Detached (분리)

    • 해당 트랜젝션이 종료되어 세션이 닫힌 객체이다.
    • 객체에 대한 참조는 유효하지만, 바로 변경은 되지 않고, 다시 연결이 되어 모든 수정 사항이 다시 동기화될 수 있다.

     

     

    사실 위의 경우를 읽어도 이해가 되지 않는 것이 당연하다.

    이는 관련 코드를 주로 타이핑 하지 않았거나, 이에 대한 배경지식이 부족하기 때문이다.

    또한 객체 상태가 변경되어 데이터베이스의 저장된 값도 변경이 되면,

    내가 지금까지 뭘한거지..? 라는 생각이 들 것이다. (필자의 경험)

     

     

    💌 소스코드

    거두절미하고, 소스코드를 보자.

    예제를 위해서 아래 클래스를 작성하였다.

     

     

    Post

    @Entity
    @Getter
    @Setter
    public class Post {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String title;
    
        @Temporal(TemporalType.TIMESTAMP)
        private Date created;
    
    
        @Override
        public String toString() {
            return "Post{" +
                    "id=" + id +
                    ", title='" + title + '\'' +
                    ", created=" + created +
                    '}';
        }
    }
    

     

     

     

     

    PostRepository

    public interface PostRepository extends JpaRepository<Post, Long> {
    
    }
    

    ※ @Repository를 붙이지 않은 이유

    JpaRepository의 구현체인 SimpleJpaRepository에서, 이미 @Repository 어노테이션을 가지고 있어, 중복되기 때문이다.

     

     

     


    Transient (과도기 상태)

      Post post = new Post();
      post.setTitle("test");

    아직 객체에 대한 영속성이 존재하지 않는다. (말 그대로 과도기 상태이다.)

    이후 post의 Id값은 Persistent로의 전이와 동시에 할당이 된다.

     

     

     

    Persistent (영속적 상태)

    Post savedPost = postRepository.save(post);

    이제 객체를 데이터베이스에 영속상태로 전이하였다.

    이후 트랙잭션의 종료까지 객체의 상태를 추적하고, 동기화시킨다.

     

     

     

    Detached (분리된 상태)

    트랙잭션의 종료된 상태로, 참조는 유효하지만, 바로 변경은 되지 않고, 다시 연결이 되어 모든 수정 사항이 다시 동기화될 수 있다.

    (자세한 예제는 아래 링크들을 참고하자)

     

     

     


    이제 간단한 테스트코드를 짜보자.

    위와 동일한 예제의 Post, PostRepository를 사용했다.

     

    테스트를 해봅시다.

     Post post = new Post();					// Transient
     post.setTitle("test");
     Post savedPost = postRepository.save(post);             // persist

    Transient의 상태의 객체 (post)를 save하여 Persistent 상태로 넘어갔다.

     

     

    assertThat(entityManager.contains(savedPost)).isTrue();
    assertThat(entityManager.contains(post)).isTrue();
    assertThat(savedPost == post).isTrue();

    이후 junit을 통해서 반환된 객체와 저장시킨 파라미터 객체를 검사를 해보았다.

    ~> 둘은 같은 객체인 것을 알 수 있다.

     

     

    post.setTitle("update1");

    자 이제 Persistent 상태에 있는 객체의 값을 변경해보았다.

     

     

    결과는 다음과 같다.

     

    post.setTitle("update1");

     

    (지금까지 ORM을 사용해본적이 없었다면 객체 값이 변할것이라고 생각하겠지만)

    놀랍게도 update 쿼리가 날라갔다.

     

     

     

    이것이 바로 JPA의 Persistent 상태이다.
    Persistent 상태의 개체에 대한 모든 변경 사항을 감지하고 작업 단위가 완료되면 해당 상태를 데이터베이스와 동기화합니다.

     

     

     

    ~> 다음 글에는 persist와 merge에 대해서 살펴봐야겠다.

     


    혹시 바로 깃헙의 코드를 참고하시고 싶으신 분들은

    아래 주소를 참고바란다. (매우 간단한 테스트이다)

    >> github.com/rojae/Springboot-JPA2/tree/aa5b1282c4722901eeb89af1a295a485c3244173

     

     

     

    🙋🏻‍♂️ 후기

    JPA, Hibernate, 나아가 ORM에 대한 학습비용은 엄청난 것 같다.

     

     

     

    🔗  참고한 글

     

    Spring Data JPA - Reference Documentation

    MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); List customers = customerRepository.findAll( where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount))); As you can see, Specifications offers some glue-code methods to chain and combin

    docs.spring.io

     

    Chapter 10. Working with objects

    Copyright © 2004 Red Hat Middleware, LLC.

    docs.jboss.org

     

    반응형
Designed by Tistory.