개발을 하다보면 Root 도메인에 종속적인 Sub 도메인을 어떻게 처리할지 고민이 될 때가 있다.
예를 들면, 재생 목록의 음악들은 재생 목록이라는 Root 도메인에 종속적인 Sub 도메인 들이다.
Delete And Insert 방식을 실행할 수도 있고, Update 방식을 사용할 수도 있다.
그러다보니 JPA를 사용했을때, 각 개발 방식이 어떻게 동작하는지 알고 싶어서 직접 엔티티를 생성하며 테스트 코드를 작성해 보았다.
코드는 모두 깃허브에 작성해두었다.
private SimpleEntity create(String name) {
SimpleEntity entity = SimpleEntity.builder()
.name(name)
.build();
return repository.save(entity);
}
우선 각 함수에 공통적으로 실행될 엔티티를 저장하는 로직은 함수로 작성했다.
BeforeEach를 사용할 수도 있지만 name을 명시하며 작성하고 싶어서 함수를 사용했다.
SAVE
@Test
@Transactional
public void 영속성_컨텍스트의_엔티티를_조회하면_SELECT_쿼리가_실행되지_않는다() {
SimpleEntity saved = create("simpleName");
repository.findById(saved.getId());
}
엔티티를 생성하고 해당 엔티티를 조회하는 함수를 실행해보자
Hibernate:
insert
into
simple
(name)
values
(?)
작성된 로직대로 라면 insert 후 select 쿼리가 실행되어야 할 것 같지만 insert 쿼리만 실행된다.
이유는 insert 쿼리를 실행하면서 해당 엔티티를 영속성 컨텍스트에 저장했기 때문에 db를 조회하지 않고 해당 엔티티를 가져온다.
UPDATE
@Test
@Transactional
public void 엔티티의_값을_변경하면_UPDATE_쿼리가_실행된다() {
SimpleEntity saved = create("simpleName");
SimpleEntity entity = SimpleEntity.builder()
.id(saved.getId())
.name("change name")
.build();
repository.save(entity);
}
엔티티를 생성하고 수정하는 테스트를 실행해보자. jpa repository 는 update 함수가 아닌 id를 지정하여 save 함수를 통해 수정해준다.
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
update
simple
set
name=?
where
id=?
예상대로 update 쿼리가 실행된다. 수정된 엔티티는 위 테스트와 같이 영속성 컨텍스트에 저장되었기 때문에 추가적인 select 쿼리를 발생시키지 않는다.
@Test
@Transactional
public void 엔티티의_값을_변경하지_않으면_UPDATE_쿼리가_실행되지_않는다() {
SimpleEntity saved = create("simpleName");
SimpleEntity entity = SimpleEntity.builder()
.id(saved.getId())
.name(saved.getName())
.build();
repository.save(entity);
}
이번엔 생성한 엔티티와 같은 값을 가지도록 한 다음 save를 실행해보자
Hibernate:
insert
into
simple
(name)
values
(?)
영속성 컨텍스트에선 저장된 엔티티와 수정된 엔티티의 값을 비교하여 수정되었을 때만 udpate 쿼리를 실행하는 것을 알 수 있다.
@Test
@Transactional
public void saveAll을_실행해도_bulk_INSERT와_UPDATE가_실행되지_않는다() {
List<SimpleEntity> list = new ArrayList<>();
list.add(SimpleEntity.builder().name("simpleName").build());
list.add(SimpleEntity.builder().name("simpleName").build());
repository.saveAll(list);
list.forEach(
entity -> entity.setName("changeName")
);
repository.saveAll(list);
}
saveAll을 사용하면 insert 혹은 update가 한 쿼리로 실행되지 않을까 해서 테스트 해보았다.
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
update
simple
set
name=?
where
id=?
Hibernate:
update
simple
set
name=?
where
id=?
jpa data repository만으로는 bulk update와 bulk insert를 수행할 수 없다. querydsl 혹은 native query를 사용하여 작성해주어야 한다.
DELETE
@Test
@Transactional
public void DELETE_BY_ID를_실행하면_각_ID마다_SELECT_쿼리가_실행된다() {
List<SimpleEntity> list = new ArrayList<>();
for (int i = 0; i < 2; i++) {
list.add(create("deleteTest"));
em.detach(list.get(i));
}
for (int i = 1; i < 2; i++) {
repository.deleteById(list.get(i).getId());
}
}
생성과 삭제는 실제로 다른 기능으로 사용될 경우가 많으므로 ( e.g. 재생목록의 음악은 추가된 후 시간이 지나고 삭제됨), 데이터 베이스에 저장되어 있지만 영속성 컨텍스트에 없는 상태를 표현하기 위해 detach를 사용했다.
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
select
simpleenti0_.id as id1_0_0_,
simpleenti0_.name as name2_0_0_
from
simple simpleenti0_
where
simpleenti0_.id=?
Hibernate:
select
simpleenti0_.id as id1_0_0_,
simpleenti0_.name as name2_0_0_
from
simple simpleenti0_
where
simpleenti0_.id=?
Hibernate:
delete
from
simple
where
id=?
Hibernate:
delete
from
simple
where
id=?
delete 함수를 실행했을때, 바로 쿼리를 실행하는 것이 아닌 select를 통해 해당 값이 존재하는지 먼저 확인하는 것을 알 수 있다. 또한 delete 하나 당 select 쿼리가 한 개씩 실행된다.
@Test
@Transactional
public void DELETE_ALL_BY를_실행하면_하나의_SELECT_쿼리를_통해_엔티티를_조회한다() {
List<SimpleEntity> list = new ArrayList<>();
for (int i = 0; i < 2; i++) {
list.add(create("deleteTest"));
em.detach(list.get(i));
}
repository.deleteAllByName("deleteTest");
}
그러면 deleteAll은 어떨까?
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
insert
into
simple
(name)
values
(?)
Hibernate:
select
simpleenti0_.id as id1_0_,
simpleenti0_.name as name2_0_
from
simple simpleenti0_
where
simpleenti0_.name=?
Hibernate:
delete
from
simple
where
id=?
Hibernate:
delete
from
simple
where
id=?
select 쿼리는 하나만 발생되고 delete 쿼리는 각각 실행된다.
알게된 것
- 영속성 컨텍스트에 저장된 엔티티는 추가적으로 select 쿼리를 발생시키지 않는다.
- update(save)와 delete만 사용해도 select 쿼리를 사용한 조회 쿼리가 추가로 실행된다.
- 영속성 컨텍스트에 저장되므로 조회 쿼리를 먼저 사용하여도 실행되는 쿼리 수는 같다.
- delete를 개별적으로 수행하는 것보다 deleteAll을 수행하면 하나의 select 쿼리가 실행된다.
- bulk 쿼리는 spring data jpa 만으로는 수행할 수 없다.
'Java > JPA' 카테고리의 다른 글
[QueryDsl] transform GroupBy (0) | 2022.08.12 |
---|---|
[JPA] JPQL, QueryDsl, Spring Data의 exists (0) | 2022.07.31 |
[JPA] Spring Data에서 save()와 saveAll()의 성능 차이 (0) | 2022.04.04 |