JPA /save , saveAll 사용시 주의 사항들

25gStroy·2022년 6월 9일
0

Springboot

목록 보기
19/41

JPA saveALl 에서 insert 이전에 select 가 일어난다!!

부제 saveAll 을 잘 모르고 사용하는 문제

한 번에 많은 데이터를 데이터베이스에 저장하려고 할 때 일반적으로 데이터 하나당 insert를 날리는 것보다 값들을 묶어서 batch insert하는 경우가 더 성능이 좋다.

그래서 흔히 사용하는 방법이 SaveAll을 사용하는데 해당 함수의 내부를 잘 모르고 사용하면 insert 쿼리 하나당 select 쿼리가 날라가거나 아니면 bulk_insert를
의도했지만 개별로 insert 쿼리가 날라가고 혹은 의도치 않게 update가 되는 경험을 할 수 있다.

Batch Insert

batch insert 를 하기위한 사전 설정 yml

spring:
  jpa:
    properties:
      hibernate:
        order_inserts: true
        order_updates: true
        jdbc:
          batch_size: 500

하지만 위와같이 설정했다고 배치 인설트가 되는 것은 아니다.

id 식별 전략을 sequence 타입으로 해야한다.

saveALL 의 내부

@Transactional
public <S extends T> S save(S entity) {
	if (this.entityInformation.isNew(entity)) {
    	this.em.persist(entity);
        return entity;
    } else {
        return this.em.merge(entity);
    }
 }

확인해보면 entity의 상태가 isNew면 persist하고 다른 경우엔 merge를 한다.

persist의 경우엔 새로운 객체이기 때문에 영속성에 추가하는 것이고 merge의 경우는 새로운 객체인지 아닌지 확인을 하고 새로운 객체면 insert 아니면 update를 한다.

즉 isNew가 false이기 때문에 계속 merge가 실행되었던 것이다.

한 트랜잭션에서 가져온 id는 영속성 관리 대상이 되어 있기 때문에 save에서 isNew 판단이 되지 않아 update 여부를 확인하기 위해 계속 select를 해왔던 것이다.

즉 결과적으로는 트랜잭션 단위를 잘 관리해야 한다.

경우에따라 트랜잭션 단위를 조정하기 어려운 경우엔 다음과 같은 방법도 있다.

@Entity
@Table(name = "some_table")
@AllArgsConstructor @NoArgsConstructor
@Getter @Builder @ToString
public class SomeEntity implements Persistable<String> { // id의 타입을 제네릭에 넣어준다.
	  ...

    @Override
    public boolean isNew() {
        return true;
    }

    @Override
    public String getId() {
        return this.id;
    }
}

아래 처럼 isNew를 강제로 true로 넣어주어도 해결할 수 있다.

save 함수 주의사항

다음과 같은 경우가 있다 한 메소드에서 여러 테이블에 save함수로 insert 또는 update를 해야하는 경우이다.
이중 하나의 테이블엔 배치시스템을 넣기때문에 saveAll 함수를 사용한다고 했을때 함수의 호출순서가 중요하다.

EX 잘못된 예


OneRepository.save(data1); 
TwoRepository.saveAll(data2);
ThreeRepository.save(data3); // entity 2와 관계맺고 있다 가정 entity3이 1 entity2가 N (폴인키소유)

위와같은 상황에서 프로그램을 실행한후 로그를보면 이상하게 분명히 다 insert 쿼리가 날라가야 정상인데
왜인지 모르게 update쿼리가 saveAll의 insert 쿼리만큼 날라가는 경우가 발생한다.
그이유는 내부적으로 flush()를 호출하면서 영속성 컨텍스트에 data3에대한 연관관계가 있는 data2 에 더티채킹되며 update 쿼리가 실행되는 것 같다.

아래와 같이 하면 더이상 update쿼리가 날라가지 않았다.


OneRepository.save(data1); 
ThreeRepository.save(data3); // entity 2와 관계맺고 있다 가정 entity3이 1 entity2가 N (폴인키소유)
TwoRepository.saveAll(data2);
profile
애기 개발자

0개의 댓글