[Spring JPA] @DynamicInsert, @DynamicUpdate 란?

최동근·2023년 2월 6일
1

JPA

목록 보기
10/13
post-thumbnail

안녕하세요 오늘은 @DynamicInsert@DynamicUpdate 에 대해 알아보겠습니다 👨‍💻

🤖 JPA 를 이용한 엔티티 등록 및 수정

스프링 JPA 를 사용시 영속성 컨텍스트 라는 논리적인 공간을 이용하여 효과적으로 데이터베이스에 접근하고 활용할 수 있습니다.
이때 엔티티를 데이터베이스에 등록하거나 데이터베이스에 있는 엔티티를 수정할 때 JPA 기본전략 엔티티의 모든 필드를 업데이트 하는 방식으로 이루어집니다.
즉, 특정 필드 값이 등록되거나 수정되지 않아도 JPA 가 쿼리를 날릴 시 null 값으로 보내게됩니다 👨‍💻
깊은 이해를 위해 예시를 들어보겠습니다.

// application.yml 설정 파일
spring:
  h2:
    console:
      enabled: true
  datasource:
    driver-class-name: org.h2.Driver
    url: h2 url
    username: sa
    password: [비밀번호]

  jpa:
    open-in-view: false
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        show_sql: true
        format_sql: true
    defer-datasource-initialization: true
// TestEntity 클래스
@Slf4j
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table
@Entity
public class TestEntity extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column
    private String testName;

    @Column
    private String testValue;

    @Column
    private String testMom;

    @Column
    private String testDad;

    @Column
    private String testBrother;
}
// TestRepository 클래스
@Repository
public interface TestRepository extends JpaRepository<TestEntity, Long> {

}

위에서 제시된 파일과 클래스를 통해 testNametestValue 값만 설정 후 엔티티를 등록하고 날라간 쿼리를 확인해보겠습니다 👨‍💻

Hibernate: 
    insert 
    into
        test_entity
        (id, created_at, updated_at, test_brother, test_dad, test_mom, test_name, test_value) 
    values
        (default, ?, ?, ?, ?, ?, ?, ?) // 모든 칼럼에 대한 쿼리 생성

JPA 구현체인 Hibernate 를 통해 데이터베이스로 날라간 쿼리를 보면
의도한 testNametestValue 칼럼을 제외하고 다른 칼럼 들에 대한 쿼리 또한 만들어진 것을 확인할 수 있습니다 🧑🏼‍💻

해당 이미지는 실제 H2 에 저장된 엔티티 결과입니다.
모든 필드를 업데이트 하는 쿼리가 날라갔지만 실제 데이터베이스에 저장된 값은 null 로 채워진 것을 확인할 수 있습니다.

이번에는 이미 저장된 엔티티에 testNametestValue 칼럼을 수정하는 로직을 만들어서 쿼리를 날려보겠습니다.

Hibernate:  // <- 엔티티 조회하는 쿼리
    select
        testentity0_.id as id1_0_,
        testentity0_.created_at as created_2_0_,
        testentity0_.updated_at as updated_3_0_,
        testentity0_.test_brother as test_bro4_0_,
        testentity0_.test_dad as test_dad5_0_,
        testentity0_.test_mom as test_mom6_0_,
        testentity0_.test_name as test_nam7_0_,
        testentity0_.test_value as test_val8_0_ 
    from
        test_entity testentity0_ 
    where
        testentity0_.test_name=?
Hibernate:  // <- 엔티티 수정하는 쿼리
    update
        test_entity 
    set
        created_at=?,
        updated_at=?,
        test_brother=?,
        test_dad=?,
        test_mom=?,
        test_name=?,
        test_value=? 
    where
        id=?

제가 구성한 로직에 대해 설명하자면 testRepository 에서 스프링 데이터 JPA 를 이용해 findByTestName 메소드를 사용하여 해당 엔티티를 조회 후 수정하도록 설계했습니다.
실제 날라간 쿼리를 통해 업데이트를 의도했던 testNametestValue 칼럼을 포함한 모든 칼럼에 대한 수정 쿼리가 날라간 것을 확인할 수 있습니다 🙆🏻

여기서 우리는 이런 의문이 생길 것입니다 🤔

왜 JPA 구현체는 불필요하게 모든 필드를 업데이트할까?

이렇개 모든 필드를 업데이트하면 데이터베이스에 보내지는 데이터 전송량이 증가한다는 단점이 있습니다. 하지만 두 가지의 장점을 얻을 수 있습니다 💪

  • 모든 필드를 사용하면 바인딩 되는 데이터만 다를 뿐 등록 , 수정 쿼리가 항상 같습니다.
    따라서 어플리케이션 로딩 시점에 쿼리를 미리 생성해두고 재사용할 수 있습니다.
  • 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용할 수 있습니다.

즉 잃는 것보다 얻는 것이 더 많기 때문에 JPA 는 기본 전략으로 모든 필드 업데이트 방식을 사용합니다 🧑🏼‍💻 하지만 어떤 경우에는 모든 필드를 업데이트 하는 것이 원치 않는 결과를 초래할 수 있습니다.
이를 위해 @DynamicInsert & @DynamicUpdate 가 존재합니다.

🤖 @DynamicInsert & @DynamicUpdate 에 대한 이해

앞에서 살펴본 봐와 같이 엔티티 등록 수정시 JPA는 모든 필드에 대한 작업을 실행합니다.
여기서 @DynamicInsert@DynamicUpdate 를 사용하면 실제 등록되거나 수정되는 칼럼에 대한 쿼리를 작성할 수 있습니다.

@DynamicInsert 실습

앞에서 실습했던 것과 동일하게 testNametestValue 필드만 값을 설정 후 엔티티를 데이터베이스에 등록해보겠습니다 👨‍🎨

Hibernate: 
    insert 
    into
        test_entity
        (created_at, updated_at, test_name, test_value, id) 
    values
        (?, ?, ?, ?, default)

해당 쿼리는 @DynamicInsert 설정 후 날라간 쿼리입니다.
여기서 우리는 실제 저장되는 필드인 testNametestValue 필드에 대한 쿼리만 날라간 것을 확인할 수 있습니다.

@DynamicUpdate 실습

마찬가지로 testNametestValue 필드만 수정하는 로직을 작성하여 테스트 해보겠습니다.

Hibernate:  // <- 엔티티 조회하는 쿼리
    select
        testentity0_.id as id1_0_,
        testentity0_.created_at as created_2_0_,
        testentity0_.updated_at as updated_3_0_,
        testentity0_.test_brother as test_bro4_0_,
        testentity0_.test_dad as test_dad5_0_,
        testentity0_.test_mom as test_mom6_0_,
        testentity0_.test_name as test_nam7_0_,
        testentity0_.test_value as test_val8_0_ 
    from
        test_entity testentity0_ 
    where
        testentity0_.test_name=?
Hibernate: // <- 엔티티 수정하는 쿼리
    update
        test_entity 
    set
        updated_at=?, // 엔티티 리스너로 인해 만들어짐
        test_name=?,
        test_value=? 
    where
        id=?
        

여기서 우리가 집중해야하는 부분은 엔티티를 수정하는 쿼리입니다.
앞에서 어노테이션을 적용하기 전과 달리 실제 수정되는 칼럼인 testNametestValue 에 대한 수정 쿼리가 만들어진 것을 확인할 수 있습니다.

🤖 @ ColumnDefault 에 대한 이해

@ColumnDefault 는 해당 필드에 값에 대한 default 값을 설정할 수 있는 어노테이션입니다.
즉, @ColumnDefault 가 선언되어 있는 칼럼의 값을 채우지 않으면 미리 default 로 지정해 놓은 값으로 칼럼이 채워집니다.
이때 별도의 설정 없이 @ColumnDefault 를 사용하면 의도했던 결과가 나오지 않습니다 🤔
그 이유는 해당 어노테이션은 필드 값이 아무 값도 저장되지 않을 때 적용할 수 있는데 JPA 기본 전략 사용 시 모든 필드에 대한 쿼리가 적용됨으로 null 값이 채워집니다. 이때 null 값 또한 값이기 때문에 @ColumnDefault 가 적용되지 않습니다.
이때 @DynamicInsert 를 사용함으로써 처음에 의도했던 결과를 얻을 수 있습니다 💪

또한 @ColumnDefault 는 사실 처음 DDL 생성시 해당 칼럼을 defalut 값으로 채워주기 위해 존재하는 어노테이션입니다 👍


참고

JPA | DynamicInsert, DynamicUpdate, 엔티티 리스너
JPA @ColumnDefault에 대한 오해, 컬럼 default 적용하기, @ColumnDefault not working 해결하기, @DynamicInsert
jpa insert 시 default 값 적용

profile
비즈니스가치를추구하는개발자

0개의 댓글