티스토리 뷰

문제 상황


유저 정보를 업데이트하는 API를 확인해 보자.

이 API는 @PatchMapping을 활용해 특정 칼럼(birth, intro, nickname, profileImage)만 업데이트하는 경우이지만

실제 UPDATE 쿼리를 확인하면 모든 컬럼에 대해 set이 들어가 있다..?

2024-08-23T22:04:40.768+09:00 DEBUG 14232 --- [moongge] [nio-8080-exec-5] org.hibernate.SQL                        : 
    /* <criteria> */ select
        ue1_0.userId,
        ue1_0.badgeList,
        ue1_0.birth,
        ue1_0.fcmToken,
        ue1_0.group_code,
        ue1_0.intro,
        ue1_0.nickname,
        ue1_0.password,
        ue1_0.profileImage,
        ue1_0.userName,
        ue1_0.userType 
    from
        UserEntity ue1_0 
    where
        ue1_0.userId=?

2024-08-23T22:04:41.883+09:00 DEBUG 14232 --- [moongge] [nio-8080-exec-5] org.hibernate.SQL                        : 
    /* update
        for com.narsha.moongge.entity.UserEntity */update UserEntity 
    set
        badgeList=?,
        birth=?,
        fcmToken=?,
        group_code=?,
        intro=?,
        nickname=?,
        password=?,
        profileImage=?,
        userName=?,
        userType=? 
    where
        userId=?

 

업데이트를 하는 로직을 확인해보면

@Override
public UserProfileDTO updateProfile(String userId, MultipartFile multipartFile, UpdateUserRequestDTO updateUserRequestDTO) {

    UserEntity user = userRepository.findByUserId(userId)
            .orElseThrow(() -> new UserNotFoundException(ErrorCode.USER_NOT_FOUND));

    ...

    // 유저 정보 업데이트
    user.updateProfile(updateUserRequestDTO.getBirth(),
            updateUserRequestDTO.getNickname(),
            updateUserRequestDTO.getIntro(),
            imageUrl);

     return UserProfileDTO.mapToUserProfileDTO(user);
}

첫 번째 쿼리인 SELECT 문은 유저 정보를 가져오기를 위한 쿼리,

그리고 우리가 봐야 하는 두 번째 쿼리인 UPDATE 문은  JPA의 변경 감지로 업데이트를 진행하고 있다. 

 

그렇다면 왜 모든 칼럼에 대해 set이 들어가 UPDATE 쿼리가 나가는지 보자.

 

문제 원인


 

JPA의 구현체 하이버네이트는 결국 JDBC API를 내부적으로 사용하는데, JDBC에서는 Statement와 PreparedStatement라는 두 가지 방법으로 쿼리를 실행할 수 있습니다. 

 

Statement는 쿼리를 실행할 때마다 쿼리문 분석 -> 컴파일 -> 실행의 과정을 반복합니다. 

반면, PreparedStatement는 처음 이 과정을 거친 후 쿼리를 캐시에 저장하여 재사용합니다. 

 

하이버네이트는 쿼리 재사용과 성능 최적화를 위해 주로 PreparedStatement를 사용하며,

쿼리에서 '?' 부분만 바꿔가면서 효율적으로 작업을 처리합니다. 

 

 

문제  해결


특정 칼럼만 업데이트를 날리고 싶다면 @DynamicUpdate를 활용할 수 있는데, 

@DynamicUpdate 어노테이션을 엔티티에 붙여주면 됩니다. 

@DynamicUpdate
public class UserEntity {
	...
}

 

UPDATE 쿼리를 확인해 보면

2024-08-23T22:26:08.110+09:00 DEBUG 17984 --- [moongge] [nio-8080-exec-3] org.hibernate.SQL                        : 
    /* update
        for com.narsha.moongge.entity.UserEntity */update UserEntity 
    set
        birth=?,
        intro=?,
        nickname=?,
        profileImage=? 
    where
        userId=?

변경된 부분만 set이 설정되어 UPDATE문이 나가는 것을 볼 수 있다. 

 

BUT...


이렇게만 놓고 보면 무조건 사용하는 것이 좋은 거 아닌가? 생각할 수 있지만

@DynamicUpdate 에 대해 찾아보면 경고하는 블로그의 글을 많이 찾아볼 수 있다. 그 이유를 알아보자.

 

위에서 "PreparedStatement는 처음 이 과정을 거친 후 쿼리를 캐시에 저장하여 재사용합니다. " 라고 했는데 

SQL 구문을 캐시하고, ? 로 작성된 파라미터 부분만 변경해 가면서 재사용하게 됩니다. 

2024-08-23T22:04:41.883+09:00 DEBUG 14232 --- [moongge] [nio-8080-exec-5] org.hibernate.SQL                        : 
    /* update
        for com.narsha.moongge.entity.UserEntity */update UserEntity 
    set
        badgeList=?,
        birth=?,
        fcmToken=?,
        group_code=?,
        intro=?,
        nickname=?,
        password=?,
        profileImage=?,
        userName=?,
        userType=? 
    where
        userId=?

 

@DynamicUpdate를 활용할 때는, 엔티티가 업데이트할 때마다 SQL문을 만들어, 이런 SQL 캐시 히트율이 떨어지게 됩니다. 

 

 

또 JPA 입장에서도 추가적인 연산이 필요해집니다. 

모든 칼럼을 수정할 때는 엔티티 객체의 변경에 대해서만 추적하면 됐지만

@DynamicUpdate를 실행하기 위해서는 변경된 컬럼을 찾기 위해서 필드 수준의 추적이 필요해지기 때문입니다. 

 

 

오히려 전체 칼럼을 하나의 PrepareStatement 스타일의 SQL을 반복해서 사용하는게, 더 속도가 빠를 수도 있다는거죠. 

즉, @DynamicUpdate 성능 오버헤드를 가지고 있고, 실제로 필요할 때만 사용을 해야 한다!

 

 

그럼 언제 사용해?...🤔 


1. 엔티티가 많은 컬럼을 가지고 있을 때 

가장 단순하고 기본적인 이유라고 볼 수 있는데, 칼럼이 많거나, 칼럼의 크기가 크거나, 길이가 너무 길거나 사용할 수 있다. 물론 "많다" 라고 하는 것은 추상적인 양이므로 잘 판단해야 할 것 같음.

++ '자바 ORM 표준 JPA 프로그래밍' 책에서는 성능 향상 기준은 컬럼 30개이상 이라고 함.

 

2. 테이블에 인덱스가 많을 때

많은 칼럼을 가지고 있는 경우와 비슷한 이유인데, 칼럼이 변경이 발생할 경우 인덱스 재정렬을 하게 됩니다. 

인덱스가 많으면 그만큼 리소스가 많이 소모된다. 

 

++ 근데 데이터가 너무 크다면 이미 테이블을 분할했지 않았을까...

 

 

👉🏻 물론 다른 다양한 이유가 있을 수도 있다. @DynamicUpdate의 사용 유무는 개인별로 상황에 맞게 결정하는 것이 좋을 듯해 보인다 

 

 

++ 내 경우는 많은 양의 필드도 아니거니와 적은 양으로 필드를 업데이트를 하는 경우가 아니므로 @DynamicUpdate을 사용하지 않기로 결정했다. 

 

 

 

 

 

Reference

https://rudaks.tistory.com/entry/spring-data-jpa%EC%9D%98-DynamicUpdate

 

spring data jpa의 @DynamicUpdate

1. 개요 Hibernate로 Spring Data JPA를 사용할 때, @DynamicUpdate와 같은 추가적인 특징을 사용할 수 있다. @DynamicUpdate는 JPA 엔터티에 적용될 수 있는 클래스 수준의 어노테이션이다. 이는 변경된 컬럼에 대

rudaks.tistory.com

https://multifrontgarden.tistory.com/299

 

@DynamicUpdate 는 언제 써야할까

JPA 를 이용하면 엔티티의 상태를 변경해주는 것만으로 update 쿼리를 실행시키게 된다. 이때 발생하는 쿼리는 모든 컬럼을 대상으로 update 를 실행한다. @Entity @Table(name = "person") public class Person { @Id

multifrontgarden.tistory.com

https://www.yes24.com/Product/Goods/19040233 

 

자바 ORM 표준 JPA 프로그래밍 - 예스24

자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고

www.yes24.com

 

'TIL' 카테고리의 다른 글

OneToOne 관계에서의 N + 1 문제  (0) 2024.09.07
JPA에서 save할 때 select 쿼리가 나가는 이유?  (0) 2024.08.23
Docker란?  (0) 2024.08.19
쿠키, 세션, 토큰 어떤 차이일까?  (0) 2024.04.23