springframework/Spring Data JPA
Spring Data JPA : Enity 저장
Jungsoomin :)
2020. 11. 18. 18:43
JpaRepository 의 save() 는 단순히 Entity 를 추가하는 메서드가 아니다.
- Transient 상태의 Entity 라면 persist() 를 호출하여 Persistent 상태로 변환한다.
- Detached 상태의 Entity 라면 merge() 를 호출하여 Attach 시켜 Persistent 상태로 변환한다.
EntityManager.merge()
- Merge() 메서드 의 매개 값인 Entity 의 복사본을 만들고, 복사본을 Persistent 상태로 돌린 후 리턴한다.
- 만약, Id 식별자가 저장된 데이터에 없는 값이라면 새로운 Entity 로 간주하고 Insert 시킨다.
판단 기준은 @Id 프로퍼티이다.
- @Id 프로퍼티가 null = Transient 상태로 간주
- @Id 프로퍼티가 null이 아니다 = Detached 상태로 간주
@RunWith(SpringRunner.class)
@DataJpaTest
class PostRepositoryTest {
@Autowired
private PostRepository postRepository;
@PersistenceContext
EntityManager entityManager;
@Test
public void test(){
Post post = new Post();
post.setTitle("JPA");
post.setCreated(new Date());
Post savedPost = postRepository.save(post); // Transient > Persistent = persist() 호출
assertThat(entityManager.contains(post)).isTrue();
assertThat(entityManager.contains(savedPost)).isTrue();
assertThat(post == savedPost).isTrue();
Post update = new Post();
update.setId(savedPost.getId());
update.setTitle("Hibernate");
update.setCreated(new Date());
Post updatedPost = postRepository.save(update); // Detached > Attach = merge() 호출
assertThat(entityManager.contains(updatedPost)).isTrue();
assertThat(entityManager.contains(update)).isFalse(); // merge() 호출 시 복사 본만 Persistent 상태로 돌린다는 것을 알 수 있다.
assertThat(update == updatedPost).isFalse();
List<Post> all = postRepository.findAll();
assertThat(all.size()).isEqualTo(1);
}
}
습관적인 쿼리 파악, 중간에 insert 가 불필요하게 반복되는 일이 있어서 쿼리를 계속 지켜보면서 원하는 결과가 도출되는지 Assertion 해보다가 프로젝트를 새로 만들고 시도했다.
Transient > Persistent => save() : Insert 문
Detached > Attach > Persistent => merge() : Update 문
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
post
(created, title, id)
values
(?, ?, ?)
2020-11-18 18:28:47.911 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [TIME] - [Wed Nov 18 18:28:46 KST 2020]
2020-11-18 18:28:47.933 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [JPA]
2020-11-18 18:28:47.948 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
Hibernate:
update
post
set
created=?,
title=?
where
id=?
2020-11-18 18:28:47.963 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [TIME] - [Wed Nov 18 18:28:47 KST 2020]
2020-11-18 18:28:47.964 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [Hibernate]
2020-11-18 18:28:47.964 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
Hibernate:
select
post0_.id as id1_0_,
post0_.created as created2_0_,
post0_.title as title3_0_
from
post post0_
2020-11-18 18:28:47.979 TRACE 3692 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([id1_0_] : [BIGINT]) - [1]
save() 호출 시 내부적으로 persist() , merge() 를 판단한다.
- 차이점이 존재하나, 공통점은 리턴받은 Entity 객체가 모두 Persistent 상태로 관리된다는 점.
- 즉 save 호출 시에는 리턴 받은 Entity 객체를 사용하는 것이 좋다.