JPA는 영속성 컨텍스트 라는 논리적인 공간을 이용하여 효과적으로 DB에 접근하고 데이터를
활용한다. 이때 엔티티를 DB에 저장하거나 DB에 존재하는 엔티티를 수정할 때 JPA의 기본전략은
엔티티의 모든 필드를 업데이트 하는 방식으로 이뤄진다.
즉, 특정 필드 값이 정의되거나 수정되지 않아도 JPA가 쿼리를 날릴 시 null 값으로 보내개 된다.
// 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, ?, ?, ?, ?, ?, ?, ?) // 모든 칼럼에 대한 쿼리 생성
의도한 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
created_at=?,
updated_at=?,
test_brother=?,
test_dad=?,
test_mom=?,
test_name=?,
test_value=?
where
id=?
업데이트를 의도했던 칼럼외 다른 칼럼을 포함한 수정 쿼리가 발생한 것을 확인할 수 있다.
왜 JPA 구현체는 불필요하게 모든 필드를 업데이트할까?
모든 필드를 업데이트하면 DB에 전송되는 데이터량이 증가한다는 단점이 있지만, 아래 두 장점을
얻을 수 있다.
즉 이점이 단점보다 크기에 JPA는 기본 전략으로 모든 필드 업데이트 방식을 사용한다. 하지만 어떤 경우에는 모든 필드를 업데이트 하는 것이 원치 않는 결과로 이어질 수 있다.
이를 위해 @DynamicInsert , @DynamicUpdate 가 존재한다.
이 애노테이션들을 사용하면 실제 등록, 수정되는 칼럼에 대한 쿼리가 발생된다.
앞선 상황에서 @DynamicInsert 를 적용할 경우
Hibernate:
insert
into
test_entity
(created_at, updated_at, test_name, test_value, id)
values
(?, ?, ?, ?, default)
실제 값이 지정된 필드인 testName 과 testValue 필드에 대한 쿼리만 날라간 것을 확인할 수 있다.
@DynamicUpdate 를 적용할 경우
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 값을 설정할 수 있는 어노테이션이다. 즉 해당
어노테이션이 선언되어 있는 칼럼의 값을 채우지 않으면 지정한 default 값으로 채워진다.
하지만, 별도의 설정 없이 이 애노테이션만을 사용하면 의도한대로 동작하지 않는다.
해당 어노테이션은 필드 값이 아무 값도 지정되지 않았을 때 적용되는데, JPA 기본 전략 사용시 모든 필드에 대한 쿼리가 적용됨으로 null 값이 채워진다. 이로 인해 @ColumnDefault 가 올바르게
적용되지 않는다. 이때 @DynamicInsert를 동반해야 의도했던 결과를 얻을 수 있다.