JPA - save() vs saveAll()

헨도·2024년 11월 28일
0

SpringBoot

목록 보기
20/23
post-thumbnail

프로젝트를 진행하면서 save() 메서드와 saveAll() 메서드를 사용했고, saveAll() 메서드가 더 빠르다는 사실도 알고있다.
하지만 정확한 원인을 모르고 있었는데, 왜 빠를까? 라는 의문이 자꾸 생기면서 찾아보게 되었다.

save() vs saveAll()

Spring JPA 를 활용한 데이터 저장을 위해 필요한 메서드에는 save() 와 saveAll() 메서드가 있다.
save() 메서드는 단일 데이터를 저장할 경우 그리고 saveAll() 메서드는 여러 데이터를 iterable 한 곳에 모아 한번에 저장할 때 사용한다.

그렇다면 save() 와 saveAll() 중 어떤 메서드가 같은 양의 데이터를 저장할 때, 빠를까?

코드 비교

save()

SimpleJpaRepository.class

@Transactional
public <S extends T> S save (S entity) {
	Assert.notNull(entry, "Entry must not be null");
    
   
   if (this.entityInformation.isNew(entity)) {
   		this.entityManager.persist(entity);
        return entity;
   } else {
   		return (S) this.entityManager.merge(entity);
   }
}

saveAll()

SimpleJpaRepository.class

@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {
	Assert.notNull(entry, "Entry must not be null");
    
    List<S> result = new ArrayList();
    
    for (S entity : entities) {
    	result.add(this.save(entity));
    }
    
    return result;
}

위 save() 와 saveAll() 의 구현 코드를 보면 Transactional 어노테이션이 공통적으로 붙어있는걸 확인할 수 있다.
이 Transactional 어노테이션에는 설정할 수 있는 속성들이 존재하는데, 이 속성들을 간단하게 살펴보면...

@Transactional 속성

  1. 트랜잭션 전파 수준을 선택할 수 있는 propagation
  2. 트랜잭션 격리 수준을 선택할 수 있는 isolation
  3. 어떤 예외가 발생했을 때 롤백 설정을 할 수 있는 rollbackFor
  4. 트랜잭션 타임아웃 시간을 설정할 수 있는 timeOut
  5. 읽기 전용 트랜잭션을 설정할 수 있는 readOnly

위 속성에서 중요하게 봐야할 것은 propagation인데, 이 속성의 기본 값은 REQUIRED로 설정되어있다.

propagation 기본 값 REQUIRED

  • 부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류, 그렇지 않다면 새로운 트랜잭션을 만든다.
  • 중간에 자식/부모 에서 rollback 발생 시, 자식과 부모 모두 rollback 처리

차이가 나는 이유

위 설명을 토대로 Transactional 어노테이션 속성이 REQUIRED 로 설정되면 부모 트랜잭션이 있는지 확인해야하는 시간이 필요
이 시간의 차이가 save 와 saveAll 메서드의 차이이다.

정리

save() 의 경우 단일 데이터를 저장하는 메소드이므로, 여러 건을 저장하기 위해선 for 문을 돌려야하는데!

1. 부모 트랜잭션이 있는지 확인
2. save() 실행

X 저장하고 싶은 데이터의 양만큼 반복

위의 과정을 저장하고 싶은 양만큼 반복해야하지만,

saveAll() 의 경우 다량의 데이터를 저장하는 메소드 이므로,

1. 부모 트랜잭션이 있는지 확인
2. saveAll() 실행

위의 과정을 딱 1번만 실행하면 되므로 시간을 아낄 수 있다.

그러므로 saveAll() 메서드가 빠른 이유이다.

질문. saveAll 의 코드에 save 를 사용하는데, 그럼 2중으로 검사하는거 아니야?

saveAll() 이 구현된 코드를 보며 나도 들었던 생각이다.

이 궁금증을 해결하기 위해서는 위 save 와 saveAll 코드를 작성할 때 첫줄에 적은 클래스 명을 기억해야하는데..

Transactional 어노테이션의 경우 AOP 프록시 기반으로 구성되어 있지만, 같은 파일에서는 AOP 적용이 안되기 때문에 saveAll 메서드 내부의 save의 Transactional 어노테이션이 적용되지 않아 위의 설명처럼 1번만 구성된다.

AOP 프록시가 적용이 되지 않는 save 메서드

public <S extends T> S save (S entity) {
	Assert.notNull(entry, "Entry must not be null");
    
   
   if (this.entityInformation.isNew(entity)) {
   		this.entityManager.persist(entity);
        return entity;
   } else {
   		return (S) this.entityManager.merge(entity);
   }
}
profile
Junior Backend Developer

0개의 댓글