티스토리 뷰

Java/jpa

@DynamicUpdate 는 언제 써야할까

LichKing 2023. 1. 10. 23:00

JPA 를 이용하면 엔티티의 상태를 변경해주는 것만으로 update 쿼리를 실행시키게 된다. 이때 발생하는 쿼리는 모든 컬럼을 대상으로 update 를 실행한다.

@Entity
@Table(name = "person")
public class Person {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id")
    private Long id;
    @Column(name = "person_name")
    private String name;
    @Column(name = "person_age")
    private int age;
    
    // 생성자 생략

    public void setAge(int age) {
        this.age = age;
    }
}

위와 같은 간단한 엔티티를 정의한 후

Optional<Person> optionalPerson = personRepository.findById(1L);
Person person = optionalPerson.get();                            
person.setAge(50);

personRepository.save(person);

엔티티 객체를 이용해 상태변경을 하면

Hibernate: update person set person_age=?, person_name=? where person_id=?

이런 쿼리가 생성되어 날아간다. 변경한 값은 age 하나뿐이지만 name 까지 update 쿼리가 날아가는걸 확인할 수 있다.

# @DynamicUpdate

변경되는 age 만 수정할 수는 없을까? 방법은 간단하다. @DynamicUpdate 라는 애노테이션을 달아주면 된다.

@Entity
@Table(name = "person")
@DynamicUpdate
public class Person {

정상적으로 변경 컬럼만 update 한다.

Hibernate: update person set person_age=? where person_id=?

참고로 @DynamicUpdate 는 JPA 스펙이 아니고 하이버네이트 기능이다. JPA 스펙으로는 변경되는 컬럼만 추적하는 기능이 없는 것 같다.

어떻게 생각하면 @DynamicUpdate 를 사용한 결과가 당연하게 느껴질 수 있다. 변경이 된 컬럼만 수정해야지 전체 컬럼을 수정하는게 위험할 수 도, 비효율적으로 느껴지기도 하기 때문이다. 하지만 비효율적으로 느껴지는 이런 동작은 사실 효율적으로 동작하기 위해 필요하다.

JDBC 를 직접 이용해서 SQL 작성해본 사람은 PreparedStatement 클래스가 익숙할 것이다. PreparedStatement 는 Statement 클래스와 달리 SQL 구문을 캐시하고, ? 로 작성된 파라미터 부분만 변경해가면서 재사용하게 된다. JPA 도 결국 내부적으로는 JDBC 와 PreparedStatement 를 사용하게 되는데, 변경된 컬럼에 대해서만 update 쿼리를 날리게 되면 이런 SQL 캐시 히트율이 떨어지게 될 것이다.

PreparedStatement preparedStatement = connection.prepareStatement( 
        "UPDATE person SET name = ?, age = ? WHERE id = ?"         
);                                                                 
preparedStatement.setString(1, "new name");                        
preparedStatement.setInt(2, 35);                                   
preparedStatement.setLong(3, 1);                                   
                                                                   
preparedStatement.executeUpdate();


그리고 JPA 입장에서도 추가적인 연산이 필요해지는데, 모든 컬럼을 수정할때는 엔티티 객체의 변경에 대해서만 추적하면 됐지만 @DynamicUpdate 를 실행하기 위해서는 필드 수준의 추적이 필요해지기 때문이다.

// 엔티티 객체의 상태비교만 하면 됐었는데
if(!beforePerson.equals(afterPerson)) { 
                                        
}                                       

// 이제 객체 내 모든 필드까지 비교해야 한다!
if(!beforePerson.equals(afterPerson)) {              
    if(beforePerson.name.equals(afterPerson.name)) { 
                                                     
    }                                                
                                                     
    if(beforePerson.age == afterPerson.age) {        
                                                     
    }                                                
}

그럼 @DynamicUpdate 는 언제 사용해야할까?

# 컬럼이 많을 때

어떻게 보면 가장 기본적이고 단순한 이유다. 컬럼이 많거나 컬럼의 크기가 클때 사용하면 좋다. 다만 "많다" 라는건 상당히 추상적이므로 잘 판단해야할 것 같다. 지극히 개인적으로 15~20개 정도로 기준을 잡고있다.

# 테이블에 인덱스가 많을 때

컬럼이 많을 때와 비슷한 이유이다. 인덱스가 걸려있는 컬럼은 변경이 발생하면 인덱스를 재정렬하게 되는데, 인덱스가 많으면 많을수록 update 쿼리에 영향을 주게된다. 값이 변경되지 않았다면 굳이 update 를 하지 않는게 update 쿼리 성능에 도움을 주게된다.

# 데이터베이스가 컬럼 락을 지원할 때

널리 사용하는 DBMS(oracle, mysql)는 컬럼 락을 지원하지 않기에 그다지 와닿지 않을 수 있다. 참고한 자료에서는 yugabyte 라는 데이터베이스를 얘기하고 있는데, 컬럼 락을 지원하는 DBMS 에서 사용하기 적절하다.

# 데이터베이스가 컬럼 버저닝을 지원할 때

사실 이것도 대중적인 DBMS 에서는 지원하지 않을 뿐더러 DBMS 내부 구현에 대한 내용이라 크게 와닿지 않는다.

# 다양한 컬럼에 대해 동시성 이슈가 발생할 때

위 3가지는 참고자료에 있는 내용들을 가져온 것이고, 이번 사례는 내가 직접 경험한 내용이다. 특정한 엔티티 A 가 있다고 가정할때 애플리케이션 요구사항에 따라 step 별로 A 의 서로 다른 필드들을 변경하는 경우이다. 모든 API 를 동기적으로 동작하게 했다면 문제가 없었을텐데 step 이라고 하는 단위가 매우 작은 단위였기 때문에 클라이언트쪽에서 비동기로 서버쪽 API 를 호출했고, 그때마다 각기 다른 컬럼을 변경하게 됐는데 T1(transaction 1)이 끝나기 전에 T2가 시작됐던 것이다. T1 과 T2 에서 각각 서로 다른 한가지 컬럼만 update 를 하는 상황에서 모든 컬럼을 update 하다보니 트랜잭션간 경합이 발생하면 먼저 실행된 트랜잭션의 변경은 무시되는 이슈가 발생했었다. 이때 @DynamicUpdate 를 활용해 문제를 해결했다.

참고자료
- https://vladmihalcea.com/how-to-update-only-a-subset-of-entity-attributes-using-jpa-and-hibernate/
- https://dzone.com/articles/when-to-use-the-dynamicupdate-with-spring-data-jpa

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함