티스토리 뷰
spring-data-jpa 를 사용하여 프로젝트를 진행중에 insert (save()) 를 할경우 해당 테이블 컬럼의 default 값이 적용되지 않는 문제가 발생하였다..
막연하게 @Entity 객체의 null 인 필드는 insert 시 제외되는줄 알았는데 그게 아니였다..
해당 소스는 GitHub 에 있다.
사실 해당필드를 원시타입으로 지정할 수도 있고 private Integer likeCount = 0; 이런식으로 초기화가 가능하지만 이렇게 진행은 하지 않았다. 왜냐하면 업데이트될 entity 를 새로 생성하였을 경우 값이 항상 default 값을 가지게 되기 떄문에.. 다른 문제가 발생하였기 때문이다.
매체 테이블이 있다고 가정하고. (like_count 는 default 값으로 0 을 주었다.)
@Entity
@Getter
@Table(name = "MEDIA")
@NoArgsConstructor
public class Media {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "idx")
private Long idx;
@Column(name = "name")
private String name;
@Column(name = "url")
private String url;
@Column(name = "like_count")
@ColumnDefault("0") //default 0
private Integer likeCount;
@Builder
public Media(String name, String url, Integer likeCount) {
this.name = name;
this.url = url;
this.likeCount = likeCount;
}
}
위 entity 를 create 하게 되면 아래와 같은 ddl 이 나온다.
create table media (
idx bigint generated by default as identity,
like_count integer default 0,
name varchar(255),
url varchar(255),
primary key (idx)
)
이제 Media 의 likeCount 값을 주지 않고 (null 이된다.) 저장을 해본다..
@Test
public void insert_test() {
Media media = Media.builder()
.name("mediaName")
.url("http://zum.com").build();
Media resultMedia = mediaRepository.save(media);
assertThat(resultMedia.getLikeCount(), Is.is(0)); //실패...
}
likeCount 필드값을 지정해주지 않았기 때문에 null 이 될테고. 그럼 default value 인 0 이 들어가는줄 알았으나.. 결과는 fail
실제 save 시 수행되는 쿼리는
Hibernate:
insert
into
media
(idx, like_count, name, url)
values
(null, ?, ?, ?)
가 되며 like_count 값의 value에 null 이들어가게 된다... 두둥
- 해결방법 1. @DynamicInsert, @DynamicUpdate
null 인 필드값이 insert 나 update 시 제외되게 하는 방법은 org.hibernate.annotations. 패키지의
@DynamicInsert (insert 시 null 인필드 제외)
@DynamicUpdate (update 시 null 인필드 제외)
를 사용한다.
@Entity
@Getter
@Table(name = "MEDIA")
@DynamicInsert
@NoArgsConstructor
public class Media {
...
}
@DynamicInsert 어노테이션을 사용하여 save 진행시 수행되는 쿼리는
Hibernate:
insert
into
media
(name, url, idx)
values
(?, ?, null)
like_count 값이 null 이기 때문에 제외된것이 확인되었다~!
@Test
public void insert_test() {
Media media = Media.builder()
.name("mediaName")
.url("http://zum.com").build();
//사실 resultMedia 의 likeCount 도 0이 되는줄 알았는데.. null 이다.
Media resultMedia = mediaRepository.save(media);
//한번더 조회를 해야 (selectMedia) likeCount 값이 0 인 entity 를 얻게된다.
Media selectMedia = mediaRepository.findById(resultMedia.getIdx())
.orElseThrow(NullPointerException::new);
assertThat(selectMedia.getLikeCount(), Is.is(0)); //성공!
}
- 해결방법 2. @PrePersist, @PreUpdate
persist 되기 직전 호출되는 어노테이션 @PrePersist 를 사용하는 방법이 있다.
@Entity
@Getter
@Table(name = "MEDIA")
@NoArgsConstructor
public class Media {
...
/**
* insert 되기전 (persist 되기전) 실행된다.
* */
@PrePersist
public void prePersist() {
this.likeCount = this.likeCount == null ? 0 : this.likeCount;
}
}
해당 entity 에 @PrePersist 어노테이션을 추가하고 수행하여야 할 로직을 작성한다.
여기에선 this.likeCount 가 null 일경우 0 으로 default 값을 주었다.
@Test
public void insert_test() {
Media media = Media.builder()
.name("mediaName")
.url("http://zum.com").build();
//insert 되기 전(persist 되기전) likeCount 값이 null 이면 0 이 되기 떄문에 resultMedia 의 likeCount 값도 0이된다.Media resultMedia = mediaRepository.save(media);
assertThat(resultMedia.getLikeCount(), Is.is(0)); //성공!!
}
- 결론
첫번째 방법 (@DynamicInsert) 과 두번쨰 방법 (@PrePersist) 을 고민하다 두번째 방법으로 진행하였다. 왜냐하면 코드로 로직을 조금이나마 더 파악할 수 있을것 같아서.. 이다.
- Total
- Today
- Yesterday
- spring-data-jpa
- fetchjoin
- 개발
- Jenkins
- gradle
- query dsl
- JUnit
- Database
- @Valid
- jpa
- DEMONIZE
- fetch join
- spring
- Limit
- 페치조인
- n+1
- web
- SpringBoot
- ec2
- error
- Validation
- Docker
- insert
- SonarQube
- QueryDSL
- Spring Boot
- orm
- hibernate
- 쿼리
- query
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |