티스토리 뷰
Jpa를 이용하면서 데이터 디폴트 값을 어떻게 줘야 할까 고민 한 적이 있었다.
첫 번째로 시도한 방법은 다음과 같다.
public class Saying{
...
@Column(columnDefinition = "VARCHAR(255) default '작자미상'")
private String author;
}
@Column에 디폴트 값을 설정하여 시도 한 적이 있었다.
그리고
resources 디렉토리 아래에 "import.sql"에 다음과 같이 작성했다.
INSERT INTO SAYING (content) VALUES ('새로운 일을 시작하는 용기속에 당신의 천재성, 능력과 기적이 모두 숨어 있다.');
그랬을 때 DB 상에서는
ID AUTHOR CONTENT
1 "작자미상" "새로운 일을 시작하는 용기속에 당신의 천재성, 능력과 기적이 모두 숨어 있다."
내가 의도한대로 값이 들어갔었다.
하지만 테스트케이스를 작성해서 돌렸을 때 author 부분에 값을 넣지 않고 저장하는 테스트를 진행하였는데 해당 테스트케이스가 실패를 한 것이다.
디버깅을 하면서 확인한 결과 해당 객체를 생성하는 방식을 빌더 패턴으로 생성했었는데 이 때 author에 null이 들어가는 것으로 인식이 되어 디폴트 값은 '작자미상'은 맞지만 null로 초기화 된것을 알 수 있다.
그래서 해결할 방법이 뭐가 있을까 고민을 해봤는데
구글링을 한 결과 하나의 방법이 있었다.
@PrePersist라는 어노테이션을 이용하면 된다.
persist() 메서드를 호출해서 엔티티를 영속성컨텍스트에 관리하기 직전에 호출 된다. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재 하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다
근데 필자의 경우에는 해당 author가 null일 때에만 작자미상으로 하고 싶은 경우이다 그러면 다음과 같이 작성해주자.
@PrePersist
public void prePersist(){
this.author = this.author == null ? "작자미상" : this.author;
}
그럼 이때 해당 값을 주었을 때에도 해당 어노테이션에 대해 어떻게 작동할까?\
@Test
void test_author_생략했을_때(){
Saying saying = Saying.builder().content("hello").build();
Saying result = sayingRepository.save(saying);
assertThat(result.getAuthor()).isEqualTo("작자미상");
}
@Test
void test_author_넣었을_때(){
Saying saying = Saying.builder().author("안녕").content("hello").build();
Saying result = sayingRepository.save(saying);
assertThat(result.getAuthor()).isEqualTo("안녕");
}
두 개의 테스트를 돌린 결과 모두 통과한 것을 볼 수 있다.
2021.12.11 추가 사항
https://github.com/YAPP-19th/iOS-Team-1-Backend/pull/108
저는 위의 내용을 이해했을 때에는 insert, update에 대해서 변화가 생길 때 db에 들어가기 전에 변화가 생긴다면 작용하는 것으로 알고 있었습니다.
근데 같이 작업하던 분께서 insert에 대해서만 작용한다는 것이었다.
그래서 저는 바로 테스트케이스를 작성해봤고 insert문에 대해서만 작동한다는 것을 확인했다.
@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Notice {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Column(columnDefinition="TEXT")
private String content;
private LocalDate createdAt;
@Builder
public Notice(String title, String content) {
this.title = title;
this.content = content;
}
@PrePersist
public void prePersist() {
this.createdAt = DateUtil.KST_LOCAL_DATE_NOW();
}
public void setContentForTest(String content) {
this.content = content;
}
}
@Test
@Transactional
void test_공지사항_prepersist() {
try(MockedStatic<DateUtil> dateUtil = Mockito.mockStatic(DateUtil.class)) {
//given
dateUtil.when(DateUtil::KST_LOCAL_DATETIME_NOW).thenReturn(LocalDateTime.of(2021,12,11,12,1));
dateUtil.when(DateUtil::KST_LOCAL_DATE_NOW).thenReturn(LocalDate.of(2021,12,11));
dateUtil.when(DateUtil::MID_NIGHT).thenReturn(LocalDateTime.of(2021,12,11,0,0)); // 토요일
Notice notice = Notice.builder().content("1").title("2").build();
Notice first = noticeRepository.save(notice);
assertThat(first.getCreatedAt()).isEqualTo(LocalDate.of(2021,12,11));
dateUtil.when(DateUtil::KST_LOCAL_DATE_NOW).thenReturn(LocalDate.of(2021,12,12));
entityManager.clear();
Notice second = noticeRepository.findById(first.getId()).orElse(null);
assertThat(second).isNotNull();
// when(dirtyChecking)
second.setContentForTest("3");
noticeRepository.save(second);
Notice third = noticeRepository.getById(second.getId());
assertThat(third.getContent()).isEqualTo("3");
assertThat(DateUtil.KST_LOCAL_DATE_NOW()).isEqualTo(LocalDate.of(2021,12,12));
//then 제가 예상했을 때는 2021-12-12인데 실제 결과는 2021-12-11이었다.
assertThat(third.getCreatedAt()).isEqualTo(LocalDate.of(2021,12,11));
}
}
흐름을 보시면 공지문을 만들고 DB에 저장한다. 그리고 다시 꺼낸 다음에 더티체킹 즉 업데이트문을 날리는 작업을 한 뒤 확인해본 결과 PrePersist가 작동되지 않는 것을 확인했다.
위의 예시에서는 기본적으로 해당 필드에 대해서 생성할 때 넣지 않기 때문에
@PrePersist
public void prePersist(){
this.author = "작자미상";
}
이런식으로 바로 대입해서 사용하였다.
주의할 점은 생성할 때 해당 값을 넣는 것이 있다면
@PrePersist
public void prePersist(){
this.author = this.author == null ? "작자미상" : this.author;
}
이와 같이 null 체크를 해주는 게 맞다.
@Test
void test_author_넣었을_때(){
Saying saying = Saying.builder().author("안녕").content("hello").build();
Saying result = sayingRepository.save(saying);
assertThat(result.getAuthor()).isEqualTo("안녕");
}
테스트케이스는 다음과 같으며 첫번째와 같이 null체크를 안하는 경우 어떤 결과가 나올까?
이와같이 prePersist에 있는 '작자미상' 설정 값이 들어가기 때문에 생성할 때 prepersist 에서 사용되고 있는 필드가 사용된다면 null의 여부에 따른 설정으로 해줘야 한다.
두번째 스타일로 적용했을 때에는 잘 된것을 확인할 수가 있다.
1. 생성할 때 @PrePersist 안에 있는 필드가 쓰이지 않는다면 null 체크할 필요가 없다.
2. 필드가 쓰일 수 있다면 null의 여부에 따른 값 설정해줘야 한다. (value = value == null ? "default" : value;)
'JVM > Spring' 카테고리의 다른 글
[JPA] JPA N+1 문제 (0) | 2021.12.03 |
---|---|
[Spring Boot] Slack Bot 연동 작업 (0) | 2021.11.01 |
[Spring] JPA (0) | 2021.10.09 |
[스프링] [WAS] [Web Server] (0) | 2021.09.30 |
[Spring Boot] Setter 그리고 update에 대해서 (0) | 2021.09.19 |
- Total
- Today
- Yesterday
- 2021 KAKAO BLIND RECRUITMENT
- Pattern
- docker-compose
- PostgreSQL
- 백준
- 그래프
- 프로그래머스
- thread
- DRF
- django
- 카카오
- Linux
- Java
- setattr
- headers
- docker
- env
- BFS
- 자바
- 면접
- 파이썬
- dockerignore
- Command Line
- postgres
- Collections
- Celery
- Spring
- Python
- 알고리즘
- ubuntu
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |