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
아래 댓글을 참고하여 수정했습니다.
안녕하세요. 심한수님
부족하지만 제가 알고 있는 지식이 도움이 되었으면 좋겠다는 마음에 답변 드립니다.
먼저 엔티티는 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
처리 됩니다.
따라서 id
를 300L
로 설정했기 때문에 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();
}
간단한 테스트를 예제로 보여드리겠습니다.
@Entity
@Setter @Getter
public class TestEntity {
@Id
private String id;
private String name;
}
@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. 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어노테이션을 사용하면 변경 감지가 일어나네요
음...
제가 생각하기에는
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. 12:48
답변 감사드립니다. 좋은 공부가 되었습니다^^