Spring Data JPA에는 save라는 entity를 저장하는 메서드가 존재한다, 그런데 save를 사용하려고하면 다음과 같이 추천메서드가 함께나오는 것을 볼 수 있다.

이렇게 4개의 메서드를 확인할 수 있다.
이제 저장메서드들에 대해 설명할 건데 설명하기에 앞서 다음의 단어들을 알아두면 도움이 될 것이다.
flush()
- 영속성 컨텍스트의 변경 내용을 DB에 동기화한다.
- Transaction commit을 하면, flush가 동작하는데 쓰기 지연 SQL 저장소의 쿼리를 수행한다.
- rollback 가능
commit
- DB에 동기화한 내용들을 영구적으로 저장하는 SQL문법이다.
- rollback 불가능
transaction
- 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위이다.
- 예를 들어 insert를 하고 select를 하는 것처럼 작업의 단위를 트랜잭션이라고 한다.
두 메서드는 저장한다는 관점에서 보면 같지만 save는 단일건을 저장하는방식이고 saveAll은 다수의 건을 한번에 처리한다는 점에서 다르다, 하지만 saveAll메서드 내용을 보면 의아한점을 발견할 수 있는데
// save 메서드
@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);
}
}
// saveAll 메서드
@Transactional
@Override
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null");
List<S> result = new ArrayList<>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
saveAll은 save를 엔터티 리스트의 크기만큼 반복시켜 저장하는 것을 볼 수 있다. 그렇다면 save를 반복해서 다량의 데이터를 처리하지 왜 saveAll메서드를 별도로 만들었을까 라고 의문이 들 수 있다, 실제로 테스트 코드를 작성해 테스트를 해보았다.
@Service
public class memberService {
private final memberRepository memberrepository;
memberService(memberRepository memberrepository){
this.memberrepository = memberrepository;
}
@Transactional
public void save(){
long time = System.currentTimeMillis();
for(int i=1; i<=10; i++){
Member member = Member.builder()
.name("tester")
.age(10 + i)
.build();
memberrepository.save(member);
}
System.out.println("clear time : " + (System.currentTimeMillis() - time) + "ms.");
}
@Transactional
public void saveAll(){
long time = System.currentTimeMillis();
List<Member> members = new ArrayList<>();
for(int i=1; i<=10; i++){
Member member = Member.builder()
.name("tester")
.age(10 + i)
.build();
members.add(member);
}
memberrepository.saveAll(members);
System.out.println("clear time : " + (System.currentTimeMillis() - time) + "ms.");
}
}
save() 메서드 결과 : 73ms
saveAll() 메서드 결과 : 18ms
코드를 실행시키고 실행시간이 얼마나 걸렸는지를 보면 이렇게 확연히 차이가 나는 것을 확인할 수 있다 고작 10건의 데이터를 넣었을 뿐인데 이렇게 차이가 나는걸 보면 100건이 아니라 10000건 데이터를 넣을때를 생각하면 어마어마한 차이이다, 대체 왜 이런결과가 나오는걸까 확인을 해보면 transaction(트랜잭션)과 관련이 있다.
save는 한번 실행 할 때마다 트랜잭션이 생성되어있는지 확인하고 없으면 생성을 해주는 작업을 매번확인을 해주기 때문에 리소스소모를 한다.
saveAll은 내부에서 save를 호출해 주기 때문에 하나의 트랜잭션으로 동작하기 때문에 리소스 소모가 적다. 그래서 여러건의 데이터를 저장 및 수정을 해야한다면 save를 여러번 반복하기 보다는 saveAll을 하는게 성능이 좋다고 할 수있다.
| 트랜잭션 존재 | 트랜잭션 미존재 |
|---|---|
| - 기존 트랜잭션에 참여 - @Transactional 이 걸려있기에 spring의 프록시 로직을 탄다. | - 트랜잭션을 생성하고 종료한다. |
| 트랜잭션 존재 | 트랜잭션 미존재 |
|---|---|
| - 기존 트랜잭션에 참여 - save()를 호출할 때 같은 인스턴스에서 내부 호출하기에 프록시 로직을 타지 않는다. | - saveAll()을 호출했을 때 트랜잭션을 생성한다. |
이제 saveAndFlush메서드와 saveAllAndFlush메서드에 대해 알아볼건데 결론부터 말하면 영속성 컨텍스트에 저장을 안하고 바로 DB에 저장한다는 차이점이다.
// saveAndFlush 메서드
@Transactional
@Override
public <S extends T> S saveAndFlush(S entity) {
S result = save(entity);
flush();
return result;
}
// saveAllAndFlush 메서드
@Transactional
@Override
public <S extends T> List<S> saveAllAndFlush(Iterable<S> entities) {
List<S> result = saveAll(entities);
flush();
return result;
}
여기서 flush는 위에서 설명한 대로 쓰기 지연 SQL저장소에 저장하고 commit을 하면 쓰기 지연 SQL저장소의 내용을 실행한다, 결국 기존의 save / saveAll 기능에 flush를 추가 했다고 보면 된다.
이제 실제로 테스트를 해봤는데 save와 saveAll를 실행했을 때와 같이 실행시간적 면에서는 다르지 않았지만 과정이 달랐다고 볼 수 있었다, 추가적으로 @Transaction 어노테이션을 제외하고 실행 했을 때의 결과도 달랐다.
(이번 테스트에는 saveAll과 saveAllAndFlush는 콘솔도 결과도 같아서 비교하지 않겠다.)
@Service
public class memberService {
private final memberRepository memberrepository;
memberService(memberRepository memberrepository){
this.memberrepository = memberrepository;
}
@Transactional
public void save(){
Member member = new Member("tester00", 10);
System.out.println("1========================1");
memberrepository.save(member);
System.out.println("1========================1");
member.setName("tester11");
System.out.println("2========================2");
memberrepository.save(member);
System.out.println("2========================2");
member.setName("tester22");
}
@Transactional
public void saveAndFlush(){
Member member = new Member("tester00", 10);
System.out.println("1========================1");
memberrepository.saveAndFlush(member);
System.out.println("1========================1");
member.setName("tester11");
System.out.println("2========================2");
memberrepository.saveAndFlush(member);
System.out.println("2========================2");
member.setName("tester22");
}
}
@트랜잭션 + saveAndFlush() 메서드 실행과정
@트랜잭션 + save() 메서드 실행과정
save(), saveAndFlush() 메서드 실행과정
먼저 트랜잭션 어노테이션을 붙인 것을 설명하자면 첫 번째 save는 처음에 insert 쿼리가 생성이 되고 두 번째 save에서는 쿼리가 생성되지 않지만 최종적으로 tester22로 업데이트를 할 때에는 update쿼리가 생성되고 tester22가 입력이 된 것을 확인 할 수 있다, 첫 번째 saveAndFlush에서는 insert에서 쿼리가 생성이되고 save와 다르게 두 번째 saveAndFlush는 update쿼리를 볼 수 있고 최종에도 save와 마찬가지로 update쿼리가 생성되고 tester22가 입력이 된 것을 확인 할 수 있다.
트랜잭션을 제외하고 실행한 경우에는 트랜잭션으로 묶여있지 않아서 마지막에 업데이트한 tester22가 DB상에 없는 것을 확인 할 수 있다.
결과적으로 용도에 맞게 사용하면 된다.

https://velog.io/@baekgom/save-saveAll-saveAndFlush