본문 바로가기

Spring boot

Entity의 Id 직접 주입 시, select 쿼리가 발생하는 이유와 isNew()오버라이딩하기 (성능 개선)

문제 발생

로직은 
1. 사람 정보를 주는 외부 api 호출 시, 응답으로 Id와 그 외 정보를 받는다.
2. 이 Id를 그대로 user entity의 id로 사용하고 그 외 정보와 함께 저장한다.

입니다. 

 

따라서,

원인은 id를 @generatedValue 으로 저장하지 않고 우리가 직접 값을 넣어줄 경우입니다.

문제는 엔티티를 저장할 때, select query가 다량으로 발생하는 것이죠.

 

100개를 저장했는데 100개의 select query + 100개의 insert query가 발생하네요!

왜인지 디버깅해서 천천히 뜯어보겠습니다.

 

동작 과정

우리는 새로운 데이터를 저장해주지만 save 호출 시, isNew()가 false이기 때문에 persist가 아닌 merge로 동작하고 있습니다. 

 

왜 isNew()가 false인건가?

isNew()는 id가 null인지만 확인해보네요. 

저희는 직접 id값을 넣어주었기 때문에 isNew()함수가 false가 되었나봅니다.

 

 

이제 merge 동작과정을 보면 

1. merge() 실행
2. 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
3. 존재하지 않으면 DB에서 조회 하고, 1차 캐시에 저장한다.
4. 조회한 엔티티의 값을 병합한다. (밀어 넣기, 수정)
5. 엔티티의 값을 반환한다.

 

데이터에 id가 있어 merge가 실행됐지만, 1차캐시에 엔티티가 없기 때문에 3번 과정인 DB 조회로 select 쿼리가 나갔던거네요!

isNew()을 오버라이딩 해주어 save()에서 merge가 아닌 persist로 실행되도록 하면 해결될 것 같습니다!

 

해결방법 & 성능개선

public class User implements Persistable<Id 타입> {
    @Id
    private Integer id;
    
    @Override
    public boolean isNew() {
        return createdAt == null;
    }
    
}

Persistable을 implements해서 isNew를 오버라이딩 해주겠습니다.

createdAt을 기준으로 null이면 save 할 경우, persist를 실행하도록 했습니다.

 

 

코드 전체보기

더보기
@Entity
@Getter
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User implements Persistable<Integer> {

    @Id
    private Integer id;

    @Column(nullable = false)
    private String name;

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    @Override
    public boolean isNew() {
        return createdAt == null;
    }
}

 

이제 select없이 insert만 제대로 나갑니다!

 

 

 

 

참고 

https://velog.io/@kws60000/jpa-insert-before-select