안녕하세요 오늘은 @DynamicInsert
와 @DynamicUpdate
에 대해 알아보겠습니다 👨💻
스프링 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> {
}
위에서 제시된 파일과 클래스를 통해 testName
과 testValue
값만 설정 후 엔티티를 등록하고 날라간 쿼리를 확인해보겠습니다 👨💻
Hibernate:
insert
into
test_entity
(id, created_at, updated_at, test_brother, test_dad, test_mom, test_name, test_value)
values
(default, ?, ?, ?, ?, ?, ?, ?) // 모든 칼럼에 대한 쿼리 생성
JPA 구현체인 Hibernate 를 통해 데이터베이스로 날라간 쿼리를 보면
의도한 testName
과 testValue
칼럼을 제외하고 다른 칼럼 들에 대한 쿼리 또한 만들어진 것을 확인할 수 있습니다 🧑🏼💻
해당 이미지는 실제 H2
에 저장된 엔티티 결과입니다.
모든 필드를 업데이트 하는 쿼리가 날라갔지만 실제 데이터베이스에 저장된 값은 null 로 채워진 것을 확인할 수 있습니다.
이번에는 이미 저장된 엔티티에 testName
와 testValue
칼럼을 수정하는 로직을 만들어서 쿼리를 날려보겠습니다.
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
메소드를 사용하여 해당 엔티티를 조회 후 수정하도록 설계했습니다.
실제 날라간 쿼리를 통해 업데이트를 의도했던 testName
과 testValue
칼럼을 포함한 모든 칼럼에 대한 수정 쿼리가 날라간 것을 확인할 수 있습니다 🙆🏻
여기서 우리는 이런 의문이 생길 것입니다 🤔
왜 JPA 구현체는 불필요하게 모든 필드를 업데이트할까?
이렇개 모든 필드를 업데이트하면 데이터베이스에 보내지는 데이터 전송량이 증가한다는 단점이 있습니다. 하지만 두 가지의 장점을 얻을 수 있습니다 💪
즉 잃는 것보다 얻는 것이 더 많기 때문에 JPA 는 기본 전략으로 모든 필드 업데이트 방식을 사용합니다 🧑🏼💻 하지만 어떤 경우에는 모든 필드를 업데이트 하는 것이 원치 않는 결과를 초래할 수 있습니다.
이를 위해 @DynamicInsert
& @DynamicUpdate
가 존재합니다.
앞에서 살펴본 봐와 같이 엔티티 등록 수정시 JPA는 모든 필드에 대한 작업을 실행합니다.
여기서 @DynamicInsert
와 @DynamicUpdate
를 사용하면 실제 등록되거나 수정되는 칼럼에 대한 쿼리를 작성할 수 있습니다.
앞에서 실습했던 것과 동일하게 testName
과 testValue
필드만 값을 설정 후 엔티티를 데이터베이스에 등록해보겠습니다 👨🎨
Hibernate:
insert
into
test_entity
(created_at, updated_at, test_name, test_value, id)
values
(?, ?, ?, ?, default)
해당 쿼리는 @DynamicInsert
설정 후 날라간 쿼리입니다.
여기서 우리는 실제 저장되는 필드인 testName
과 testValue
필드에 대한 쿼리만 날라간 것을 확인할 수 있습니다.
마찬가지로 testName
과 testValue
필드만 수정하는 로직을 작성하여 테스트 해보겠습니다.
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=?
여기서 우리가 집중해야하는 부분은 엔티티를 수정하는 쿼리입니다.
앞에서 어노테이션을 적용하기 전과 달리 실제 수정되는 칼럼인 testName
과 testValue
에 대한 수정 쿼리가 만들어진 것을 확인할 수 있습니다.
@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 값 적용