인프런 커뮤니티 질문&답변

심한수님의 프로필 이미지

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

쓰기지연SQL저장소는 정말 스냅샷과 비교하나요?

23.02.21 22:50 작성

·

772

·

수정됨

3

[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)

CrudRepository의 Save 함수는 파라미터로 전달받은 객체의 PK컬럼의 값 존재 유무에 따라서 값이 없을 경우 persist 함수를 이용해, 값이 있을 경우 merge 함수를 통해 객체를 영속화 하는 메서드로 이해 하고 있습니다. 우선 저는 이러한 엔티티를 가지고 있습니다.

package my.test.testproject.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;

@Entity
@Getter
@Setter
@ToString
@Table(name = "person")
@DynamicInsert
@DynamicUpdate
public class Person {
    @Id
    Long id;

    String name;

    public Person() {
    }

    public Person(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

그리고 이러한 코드가 실행이 됩니다

@Test
@Transactional
void PersonTest9() {
    Person person = new Person(300L, "Firmino");
    personRepository.save(person);
}

참고로 Id가 300인 데이터는 이미 실제 데이터베이스에 존재하고 있습니다.

이 경우에 save를 호출할 경우 PK값이 존재하기에 merge를 호출하고 객체가 영속화 되어서 트랜잭션이 커밋되면 변경감지를 통해 쓰기지연SQL저장소에 SQL이 생성이 될텐데 바로 이 SQL이 생성되는 과정에서 궁금한 점이 있습니다. 이 트랜잭션에서 Id가 300인 엔티티가 처음 영속화 되었습니다. 쓰기지연 SQL저장소에 SQL을 생성할때 스냅샷과 비교해서 SQL을 생성하는것으로 알고 있는데 그렇다면 이경우에는 제 예상에는 Insert 쿼리가 생성이 되어서 실행시 에러가 나야할것 같은데 실제로 실행을 해보면 select쿼리가 실행됩니다

Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from person person0_ where person0_.id=?

결국 PK에 값이 있는 엔티티가 merge를 통해서 영속화 되었을때 이것을 쓰기지연 SQL저장소에 SQL을 만들때 스냅샷이 아닌 실제 데이터베이스와 비교해서 쿼리를 생성하는 것입니까? 제가 잘못 이해하고 있는 부분들이 있다면 알려주시기 바랍니다ㅠㅠ

답변 1

3

개발하는쿼카님의 프로필 이미지

2023. 02. 22. 02:43

아래 댓글을 참고하여 수정했습니다.

 

안녕하세요. 심한수님

부족하지만 제가 알고 있는 지식이 도움이 되었으면 좋겠다는 마음에 답변 드립니다.

 

먼저 엔티티는 PK로 관리되는것으로 알고 있습니다.

그리고 em.merge()준영속(detach) 상태의 엔티티를 통해 영속(managed) 상태의 새로운 엔티티를 반환 해주는 메소드로 알고 있습니다.

 

merge()를 사용하게 되면 select 쿼리가 발생하고, 조회한 객체를 반환합니다.

즉, merge()로 반환되는 새로운 객체가 영속(managed) 상태 입니다.

풀어서 이야기하면, merge(entity)는 entity를 영속화 시키는 것이아니라, newEntity를 반환하며 newEntity가 영속 상태라는 말씀을 드립니다.

 

하지만, 심한수님의 경우에는 처음부터 save() 메소드에서 merge()를 실행하게 됩니다.

심한수님의 경우에 save()메소드에서 merge()를 실행하는 이유는 다음과 같습니다.

@Transactional
@Override
public <S extends T> S save(S entity) {

   Assert.notNull(entity, "Entity must not be null.");

   if (entityInformation.isNew(entity)) {
      em.persist(entity);
      return entity;
   } else {
      return em.merge(entity);
   }
}

isNew() 메소드는 엔티티의 PK가 null 이거나 원시타입인 경우는 초기값(0) 인 경우만 true, 그외는 false 처리 됩니다.

따라서 id300L로 설정했기 때문에 merge() 메소드를 실행합니다.

(초기값이 null이 아니기 때문입니다.)

 

merge 메소드 내부를 보면 설정한 id값으로 select 를 하여

해당 엔티티를 조회하고 영속상태의 새로운 엔티티를 반환 합니다.

(fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, MergeEventListener::onMerge); 메소드 까지는 어떻게 동작하는지 저도 잘 모르겠습니다. ㅠㅠ)

private Object fireMerge(MergeEvent event) {
   try {
      checkTransactionSynchStatus();
      checkNoUnresolvedActionsBeforeOperation();
      // select 쿼리 실행
      fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, MergeEventListener::onMerge );
      checkNoUnresolvedActionsAfterOperation();
   }
   catch ( ObjectDeletedException sse ) {
      throw getExceptionConverter().convert( new IllegalArgumentException( sse ) );
   }
   catch ( MappingException e ) {
      throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) );
   }
   catch ( RuntimeException e ) {
      //including HibernateException
      throw getExceptionConverter().convert( e );
   }

   return event.getResult();
}

 

 

간단한 테스트를 예제로 보여드리겠습니다.

TestEntity

@Entity
@Setter @Getter
public class TestEntity {

    @Id
    private String id;
    private String name;
}

 

SaveTest

@SpringBootTest
public class saveTest {

    @Autowired
    TestEntityRepository repository;

    @Test
    void jpaSaveTest() {
        // uuid 생성
        String id = UUID.randomUUID().toString();

        TestEntity testEntity = new TestEntity();
        testEntity.setId(id);
        testEntity.setName("비영속상태 객체 생성");

        /**
         * merge를 실행합니다.(persist가 실행되지 않습니다.)
         *
         * save() 메소드에 있는
         * isNew() 메소드는
         * 엔티티의 PK가 null 이거나
         * 원시타입인 경우는 초기값(0)인 경우만 true,
         * 그외는 false 처리 됩니다.
         */
        TestEntity newEntity = repository.save(testEntity);
        System.out.println("name1="+newEntity.getName());

        /**
         * newEntity 영속(managed) 상태가 아닙니다.
         * 그 이유는 save() 이후 커밋을 완료했기 때문입니다.
         * save()를 보면 @Transactional이 있는 것을 확인할 수 있습니다.
         * 따라서 save() 즉, merge()를 통해 반환되는 newEntity는 비영속(new) 상태 입니다.
         * 그 결과 변경감지가 일어나지 않습니다.
         */
        newEntity.setName("변경감지 확인");
        System.out.println("name2="+newEntity.getName());
    }
}

 

 

결과

Hibernate: 
    select
        testentity0_.id as id1_5_0_,
        testentity0_.name as name2_5_0_ 
    from
        test_entity testentity0_ 
    where
        testentity0_.id=?

Hibernate: 
    insert 
    into
        test_entity
        (name, id) 
    values
        (?, ?)

name1=비영속상태 객체 생성
name2=변경감지 확인

 

감사합니다.^^

 

 

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 12:48

답변 감사드립니다. 좋은 공부가 되었습니다^^

개발하는쿼카님의 프로필 이미지

2023. 02. 22. 12:59

덕분에 저도 좋은 공부했습니다.

감사합니다.^^

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 13:28

제가 학습한것을 정리하다 보니 궁금한 것이 있는데 강의 자료중 영속성 컨텍스트의 라이프 사이클 그림을 보면 merge를 호출하면 다시 영속성 상태가 된다고 했는데 여기서는 비영속이 되는 이유는 무엇입니까?

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 13:30

위의 설명에서도 풀어서 이야기하면, merge(entity)는 entity를 영속화 시키는 것이아니라, newEntity를 반환하며 newEntity가 영속화 상태라는 말씀을 드립니다. 그러면 newEntity는 영속화 상태이어야 하는거 아닙니까 왜 비영속화 상태입니까?

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 13:32

설명과 소스의 주석이 다르게 된 상황의 이유가 무엇인가요? Update쿼리가 실행되지 않고 있으니 비영속이 맞는거 같긴한데 라이프 사이클 그림에서도 merge 메서드는 영속화 시킨다고 했는데 왜 비영속인지 궁금합니다

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 13:35

인터넷에서 다른 예제를 찾아봐도 merge를 한 객체는 영속화 된다고 나와잇는데 이 소스에서는 비영속 상태이네요...... 이유가 너무 궁금합니다

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 13:43

이유를 알게 된것같습니다. newEntity의 변경감지가 일어나지 않은 것은 @Transactional어노테이션이 없는 상태에서 repository.save(testEntity)를 호출하면 해당 메서드는 데이터베이스 트랜잭션 내에서 실행되지 않기 때문에 엔티티가 영속성 컨텍스트에서 관리되지 않는 비영속 상태이기 때문이네요. @Transactional어노테이션을 사용하면 변경 감지가 일어나네요

개발하는쿼카님의 프로필 이미지

2023. 02. 22. 14:39

음...

제가 생각하기에는

newEntity 영속(managed) 상태가 아닙니다.

그 이유는 testEntity는 비영속(new) 상태 입니다.

비영속 상태를 영속상태로 만들기 위해서는 persist()를 해야하는데 위에서 save()메소드에서 merge()를 실행합니다.

 

merge()준영속(detach) 상태를 다시 영속 상태로 만드는 메소드 입니다.

testEntity는 애초에 영속상태가 된적이 없기 때문에 merge()를 통해 반환되는 newEntity는 비영속(new) 상태 입니다.

그 결과 변경감지가 일어나지 않는 것으로 보입니다.

 

https://jaime-note.tistory.com/65
여기를 참고하시면 좋을것 같습니다..!

 

감사합니다. 

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 16:35

네 블로그의 내용처럼 여기 이 테스트에서의 testEntity는 비영속객체입니다. 그러나 여기에 @Transactional을 사용하게 되면 영속상태입니다. 이유는 엔티티매니저는 한 트랜잭션내에서 유효합니다. 현재 이 메서드는 트랜잭션을 사용하지 않았고 save를 호출하는 시점에서 커밋이 발생하고 트랜잭션이 종료됩니다. 트랜잭션이 종료되면 당연히 엔티티매니저도 끝나고 때문에 testEntity는 비영속 객체인것입니다. 하지만 @Transactional을 사용하게 된다면 save를 호출하고 나서도 아직 트랜잭션이 끝나지 않았습니다 그래서 그때의 testEntity는 영속객체인것입니다. 그래서 @Transactional을 사용하면 newEntity.setName("변경감지 확인");에 대해 변경감지가 일어나고 Update쿼리가 실행되는 것을 확인 할 수 있습니다. JPA에 대해 공부할때 엔티티매니저는 한 트랜잭션내에서 유효하다는것을 그냥 흘려들었던게 큰 후회가 되네요 ㅋㅋㅋ 이번일을 계기로 엔티니매니저와 트랜잭션의 관계에 대해서 뼈저리게 깨달았습니다

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 16:38

아 백기선님의 유튜브 영상에도 JPA와 트랜잭션에 대한 좋은 퀴즈와 해결이 있으니 그것도 좋은 참고가 됩니다.

심한수님의 프로필 이미지
심한수
질문자

2023. 02. 22. 16:43

블로그의 내용도 정말 많은 도움이 되었습니다 Persistable에 대해 또 많이 공부해야 겠네요 공부할게 참 많네요

개발하는쿼카님의 프로필 이미지

2023. 02. 22. 17:26

그러게요!

많이 배웠습니다.!!

우리 화이팅 합시당^^