지나공 : 지식을 나누는 공간

JPA @ColumnDefault에 대한 오해, 컬럼 default 적용하기, @ColumnDefault not working 해결하기, @DynamicInsert 본문

우당탕탕 삽질기

JPA @ColumnDefault에 대한 오해, 컬럼 default 적용하기, @ColumnDefault not working 해결하기, @DynamicInsert

해리리_ 2021. 3. 16. 16:25

회원가입 API를 개발하던 중, @ColumnDefault가 작동되지 않는 문제를 직면했었다.

어제 내 시간을 뺏어가버린......

 

현재 상황 :

회원가입 API에서 request DTO에서는 email, password만 받고, User의 또다른 컬럼인 privateYn은 request에서 값을 받지 않으므로 Entity로 매핑했을 때 privateYn이 null이 된다. 하지만 privateYn은 not null이고 default값인 'N'로 저장되어야 한다. 현재 상황에서 저장을 하면 privateYn이 not null 이어야 하는데 null이라면서 에러가 발생한다.

 

그럼 어떻게 해야, request dto에서 값을 받지 않아서 null이 된 필드를 default값으로 변경해서 not null 조건에 만족하도록 DB에 넣을 수 있을까? (해결방법은 시도4 참고)

 

결론적으로는, 내가 @ColumnDefault의 역할을 완전히 오해하고 있었다.

 

default가 있고 not null이어야 하는 컬럼에 @ColumnDefault를 쓰면, @RequestBody로 받은 DTO에서 해당 컬럼의 값이 들어오지 않아 null로 되었을 때 그 값이 어노테이션에 설정해둔 default 값으로 바뀌는 줄 알았다. 그리고 그 상태로 DB에 save하는 줄 알았다.

 

그런데 그게 아니라, application.yml에서 jpa.hibernate.ddl-auto : create-drop 로 설정하고 서버를 구동시켰을 때 생성된 DDL을 보면 그때 default가 적용되어 있는 걸 확인할 수 있다. 즉, 저 어노테이션은 auto-ddl을 사용할 때에 적용되는 것이다. (본 포스팅의 마지막 부분에 자세하게 적혀 있다.)

 

시도 1 : 엔티티 클래스에서 @Column으로만 명시 & DB에서 DDL에 default로 'N' 명시

시도 2 : 엔티티 클래스에서 해당 컬럼에 @ColumnDefault("N")을 설정하고, DDL에 default 해둔 것도 유지

시도 3 : 엔티티 클래스에서 해당 컬럼에 @ColumnDefault("'N'")으로 명시해봄 & DB DDL 유지

시도 4 : 엔티티 클래스에서 해당 컬럼은 @Column으로 되돌리고 entity 자체에 @DynamicInsert 추가, DDL 유지 (해결)

의문 : 그럼 @ColumnDefault의 역할은 뭐야?

 

아래는 hibernate의 @ColumnDefalt 쓰는 방법이 적힌 레퍼런스다.
docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/annotations/ColumnDefault.html

docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#schema-generation-column-default-value

 

 

일단 Entity 와 DB는 이렇게 되어있다. 손봐야 할 컬럼은 privacyYn이다.

지금 만들고 있는 서비스는 커뮤니티 앱이고, privacyYn은 계정 비공개 여부를 나타내기 때문에 default 값이 'N'이어야 한다.

 

Entity 클래스

 

DB 테이블 DDL

 

시도 1 : 엔티티 클래스에서 @Column으로만 명시 & DB에서 DDL에 default로 'N' 명시

API를 호출해보면, 

 

이렇게 privateYn이 null로 된다.

 

그래서

실행되는 쿼리문과 에러는 각각 아래와 같다.

 

Hibernate: 
    insert 
    into
        user
        (created_date, last_modified_date, email, nickname, password, privacy_yn) 
    values
        (?, ?, ?, ?, ?, ?)

 

java.sql.SQLException: Column 'privacy_yn' cannot be null

 

그러니까 request로 값이 들어오지 않은 필드인 privateYn은 null인 상태이고, DDL에 의하면 privateYn은 not null 이면서 default가 'N'인데 null로 들어왔으니 cannot be null 예외가 발생한다.

 

 

 

시도 2 : 엔티티 클래스에서 해당 컬럼에 @ColumnDefault("N")을 설정하고, DDL에 default 해둔 것도 유지.

시도 3 : 엔티티 클래스에서 해당 컬럼에 @ColumnDefault("'N'")으로 명시해봄 & DB DDL 유지

 

시도 2, 3은 생략하겠다. 맨 아래 @ColumnDefault의 역할을 보면 이게 왜 안되는 지 바로 알 수 있기 때문이다.

에러 내용과 실행된 쿼리문도 모두 시도 1과 동일하다.

 

 

시도 4 : 엔티티 클래스에서 해당 컬럼은 @Column으로 되돌리고 entity 자체에 @DynamicInsert 추가, DDL 유지 (해결)

 

 

이렇게 하면 성공한다. 실행된 모습을 살펴보면, 

 

일단 들어오는 DTO와 매핑된 User 객체에서 privateYn은 똑같이 null이다.

다만 실행된 쿼리문이 다르다.

잘 보면 priavteYn이 빠져 있다.

 

@DynamicInsert : insert 시 null 인 필드 제외

@DynamicUpdate : update 시 null 인 필드 제외

 

이렇게 되면 db에 insert 될 때 default값으로 들어가게 된다.

두 쿼리문의 차이가 좀 와닿지 않아서 DataGrip을 켜고 두 개의 쿼리문을 다시 실행시켜 봤다.

 

첫째 쿼리문을 실행시키면 결과는 아래와 같다.

insert into user (email, password, nickname, privacy_yn) values ('dfdfdsf', 'adfdf', 'dfdafdf', null);

 

privateYn을 null로 명시해서 insert문을 실행하면 위와 같은 에러가 발생한다.

 

 

둘째, 

이렇게 privateYn에 대해 아무런 명시가 없는 쿼리문을 실행하면 에러 없이 privateYn이 설정된 default값으로 잘 들어간다.

 

 

즉, @DynamicInsert를 사용해서 아예 null값이 되었을 때 쿼리문에서 제외되도록 해야, DDL에 설정한 default값으로 잘 들어가진다.

 

 

 

의문 : 그럼 @ColumnDefault의 역할은 뭐야?

다 해보고 나니 생긴 의문은 이거였다. 내가 익히 알아 온 @ColumnDefault의 역할은 언제 발휘되는 건지?

근데 레퍼런스를 읽어보니 create문을 자동 생성해줄 때 그 역할이 적용되는 것 같다.

 

그래서 해 봤다. 새 프로젝트를 파고 application.yml의 내용을 jpa.hibernate.ddl-auto : create-drop으로 설정해서 서버 구동 시 table이 있으면 전부 drop하고 새로 create ddl을 생성하게 했다.

 

그랬더니 실행된 쿼리문은 아래와 같았다.

default가 잘 적용된 쿼리문의 모습

 

 

728x90
Comments