엔티티의 특정 필드에 @ColumnDefault 를 이용하여 Default 값을 'N' 으로 설정하였는데 null 값으로 저장되는 이슈가 발생하였다. 해결방법에 대해 정리해보자.
@ColumnDefault 동작
@ColumnDefault는 Hibernate 에서 제공하는 Annotation 으로, Entity 의 필드에 대해 데이터베이스 기본 값을 정할 때 사용한다. 예를 들어 isDeleted 라는 필드가 있고 기본 값을 'N' 으로 설정하고 싶을 땐 아래와 같이 설정하면 된다.
@ColumnDefault("'N'") // default N
private String isDeleted;
그런데 이렇게 설정하고 테스트를 해보면 기대했던 N 값이 아닌 null 값임을 확인할 수 있다.
- Test Code
@Test
@DisplayName("그룹 엔티티 내 isDelete 필드의 디폴트 값은 'N' 이다.")
void groupColumnDefaultTest() {
// given
GroupInfo groupInfo = GroupInfo.builder()
.groupName("group1")
.groupDesc("groupDesc")
.build();
// when
GroupInfo savedGroup = groupRepository.save(groupInfo);
// then
GroupInfo findGroup = groupRepository.findById(savedGroup.getId()).orElseThrow();
assertEquals("N", findGroup.getIsDeleted());
}
- 실행 결과
로그를 확인해보면 group_info 의 is_deleted 필드에 default 'N' 설정이 잘 들어가있기 때문에 혼란스러울 수 있지만 INSERT DATA 를 보면 isDeleted 필드가 null 값임을 확인할 수 있다.
즉, default 설정은 값이 없을 때를 가정하는데 null 이라는 값이 있게되면 무용지물이 되게 된다.
해결방안
해결방안은 멀리서 찾을 필요 없이 공식 문서에 잘 나와있다.
1. @DynamicInsert 사용하기
DynamicInsert 는 엔티티 내 null 값인 필드를 제외하고 Insert 한다. 그렇기 때문에 ColumnDefault 로 설정한 값이 null 값으로 덮이지 않고 테스트가 성공하는 것을 볼 수 있다.
- GroupInfo Entity
@Table @Entity @Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicInsert // ★
public class GroupInfo {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String groupName;
private String groupDesc;
@ColumnDefault("'N'")
private String isDeleted;
@Builder
public GroupInfo(Long id, String groupName, String groupDesc) {
this.id = id;
this.groupName = groupName;
this.groupDesc = groupDesc;
}
}
- 실행 결과
2. @Generated 사용하기 ( org.hibernate.annotations.* )
Generated 내 GenerationTime 를 통해 테이터 베이스에 의해 생성되거나 수정된 값이 엔티티의 특정 속성에 자동으로 반영되도록 지정할 수 있다.
@ColumnDefault("'N'")
@Generated(GenerationTime.INSERT)
private String isDeleted;
그런데 여기서 GenerationTime 이 Deprecated 되었다. Generated 내 다른 속성을 통해 기본 값을 지킬 수 있는 지의 여부는 잘 모르겠다. 테스트는 성공이지만 Deprecated 되었다는 것은 알아두자.
3. @PrePersist 사용하기 with BaseEntity
사실 나는 isDeleted 필드를 BaseEntity 내 필드로 사용하고 있다. 또한 기획 상 거의 모든 Entity 에 Soft Delete 를 적용중이라 모든 Entity 에 @DynamicInsert 를 설정해야했다.
이는 코드 복잡성을 증가시킨다고 생각하여 @PrePersist 를 사용해보았다.
@PrePersist 는 Entity 가 DB 에 Insert 되기 전에 호출된다.
..
public abstract class BaseEntity{
..
private String isDeleted;
@PrePersist
public void prePersist() {
if (isDeleted == null) {
isDeleted = "N";
}
}
}
정리
@ColumnDefault 를 유효하게 사용하기 위해서는 결국 @DynamicInsert 와 추가 @PrePersist 설정 등의 방법을 같이 써야하며 무엇이 더 이점이 있을 지는 프로덕션 코드에 맞는 테스트를 진행해봐야할 것 같다.
참고
https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/annotations/ColumnDefault.html
'Web' 카테고리의 다른 글
[Spring] Logback MDC 로 애플리케이션 로깅하기 (0) | 2024.04.20 |
---|---|
[JPA] deleteAll() 과 deleteAllInBatch() (0) | 2024.03.17 |
[Spring] Soft Delete with JPA (0) | 2024.01.14 |
[Spring] QueryDsl 로 동적 쿼리 짜기 with JPA (0) | 2024.01.07 |
[JPA] 양방향 순환 참조 (0) | 2023.12.24 |