[Spring Data JPA] save(), saveAll()의 차이점

simhani1·2025년 4월 28일

Backend

목록 보기
3/6
post-thumbnail

개요

save()와 saveAll()은 Spring Data JPA가 JpaRepository 인터페이스를 통해 기본 제공하는 메서드입니다. 이 글에서는 두 메서드의 동작 방식과 차이점을 정리합니다.

애플리케이션 코드

먼저 간단한 회원 엔티티를 생성하고 Repository 인터페이스를 선언합니다.

@Entity
@Getter
@Table(name = "member")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "member_id", nullable = false)
	private Long id;

	@Column(name = "money")
	private int money;

	public Member(int money) {
		this.money = money;
	}
}
public interface JpaMemberRepository extends JpaRepository<Member, Long> {
}

예시 상황

만약 회원 엔티티 1,000개를 한번에 저장해야 한다면, 아래처럼 두가지 방법으로 구현할 수 있습니다.

  • 방법1
for (int i = 1; i <= 1000; i++) {
	memberRepository.save(new Member(i));
}
  • 방법2
List<Member> members = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
	members.add(new Member(i));
}
memberRepository.saveAll(members)

방법1 처럼 구현할 수 있으면 굳이 saveAll() 함수는 왜 있는걸까요? 내부 구현을 살펴보면 차이점을 발견할 수 있습니다.

JpaRepository 인터페이스의 구현체

Spring Data JPA를 사용할 때 상속 받는 JpaRepository는 CrudRepository 인터페이스를 상속받게 되어있습니다.

그리고 CrudReposiotry 인터페이스의 기본 구현체는 SimpleJpaRepository입니다. 즉 save(), saveAll()의 내부 구현은 SimpleJpaRepository 클래스에서 찾을 수 있습니다.

save(), saveAll() 내부 구현

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

	Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);
    
	if (entityInformation.isNew(entity)) {
		entityManager.persist(entity);
		return entity;
	} else {
		return entityManager.merge(entity);
	}
}
@Transactional
@Override
public<SextendsT> List<S> saveAll(Iterable<S> entities) {

   Assert.notNull(entities,"Entities must not be null!");

   List<S> result =newArrayList<>();

	for(S entity : entities) {
	      result.add(save(entity));
	   }

	return result;
}

두 메서드를 보면 모두 @Transactional 어노테이션이 적용되어 있습니다. 만약 방법1로 사용하게 된다면 반복 횟수만큼 논리 트랜잭션을 생성하게 됩니다. 하지만 saveAll()은 클래스 내부에서 save()를 호출하고 있습니다. @Transactional은 AOP를 기반으로 동작하기 때문에, 같은 클래스 내부에서 메서드를 직접 호출할 경우 트랜잭션 프록시가 적용되지 않습니다. 따라서 saveAll() 메서드가 내부적으로 save()를 호출하더라도, 각각의 save() 호출에 대해 별도의 트랜잭션이 생성되지 않고, saveAll()이 시작할 때 만들어진 하나의 트랜잭션이 그대로 유지됩니다.

결과적으로 방법1 처럼 외부에서 반복문을 돌며 save()를 호출하면 매번 새로운 트랜잭션을 생성하게 되어 성능 오버헤드가 발생하지만, 방법2 처럼 saveAll()을 사용하면 하나의 트랜잭션 안에서 여러 저장 작업을 처리할 수 있어 오버헤드를 줄일 수 있습니다.

0개의 댓글