Floney

[플로니] default 설정을 했는데 null이 나와요

딤섬뮨 2023. 5. 9. 16:50
728x90

최근 시작한 프로젝트에서, Entity의 column값을 default값으로 설정하고픈 일이 굉장히 많아졌다.
예를 들면, 논리적 삭제를 구현할 때에 status를 boolean값으로 default는 True를 지정해주어야 했다.
그런데 원하는 대로 default값이 아닌 null로 들어가는 현상이 많았고, 이에 대한 시행착오를 적어보려고 한다.
 

@ColumnDefault()


일단 이 어노테이션에 어려움을 느낀 사람이 참 많았다. 나도 그렇고, 사람들도 그렇듯이 default가 있어서, @ColumnDefault값을 쓰면, entity에 저장될 때 자동으로 어노테이션 값으로 바뀌는 줄 알았다.

@ColumnDefault("true")
private Boolean active;

 
다음과 같은 상황일 때, active값을 명시하지 않더라도, Entity저장 시 True(1)로 들어가는 줄 알았다.

@Test
public void insert_test(){
    User user = new User("이름"); // active는 따로 넣어주지 않았다.
    User saved = userRepository.save(user);
    Assertions.assertThat(saved.getActive()).isEqualTo(true);

}

다음 test를 돌려보면, null이 뜬다.

이 어노테이션 @ColumnDefault는 table을 처음 생성할 때,  default값을 생성해 주는 역할이다.
다음은 첫 table을 create 하면 찍히는 DDL이다. active의 default가 잘 설정되어있다.

Hibernate: create table user (id bigint not null auto_increment, active bit default true, name varchar(255), primary key (id)) engine=InnoDB

하지만 hibernate의 테이블 생성 속성이 create-drop일때만 default 쿼리가 날라간다.

즉, hibernate의 create-drop을 사용하지 않는다면 무용지물일 것이다. 또한 어차피 운용시에는 jpa에 테이블 생성을 맡기지 않기에 정말 쓸모 없는 @columnDefault일 것이다.



아 그러면 이 기능은 알겠다. 근데 내 궁금증은 이것이다. 아니 DB에서도 default 설정을 잘해줬는데 왜 이후에 아무것도 안 넣었는데 status는 null로 표시되는가..
이유는 여러 방면에서 지식이 없었기 때문이다.
 

몰랐던 사실


(1) MySQL기준, null도 '값'으로 받아들인다.
default값은 아예 아무 값도 insert 되지 않은 칼럼에 대해서 적용이 된다.
그래서 null이 들어오면, default는 일단 null이든 값이 들어왔으니 default가 아닌 null값을 저장시켜주는 것이다.
 
MySQL :: MySQL 8.0 Reference Manual :: 9.1.7 NULL Values

MySQL :: MySQL 8.0 Reference Manual :: 9.1.7 NULL Values

The NULL value means “no data.” NULL can be written in any lettercase. Be aware that the NULL value is different from values such as 0 for numeric types or the empty string for string types. For more information, see Section B.3.4.3, “Problems with

dev.mysql.com

 
 
 
(2) Java는 객체를 초기화할 때, 선언해주지 않으면 null을 넣는다
 
Java에서 객체를 초기화할 때, 해당 객체의 인스턴스 변수가 초기화되지 않은 경우, 그 변수는 자동으로 해당 타입의 기본값으로 초기화됩니다. 객체 타입의 기본값은 null입니다

정말 기본인데, 왜 잊고 살았지... 우선 java의 객체 생성 시 초기화를 거치지 않으면 java는 객체의 변수들에 null로 넣어준다

    public static CreateCategoryRequest createRootRequest() {
        return CreateCategoryRequest.builder()
            .bookKey(BOOK_KEY)
            .build();
    }

다음과 같은 Builder에 name을 설정해주지 않으면

    @Test
    void null_test(){
        Assertions.assertThat(createRootRequest().getName()).isNull();
    }

null으로 초기화된다

JPA에서는 객체 필드값에 값이 없다면 null을 insert문에 날려준다.
 
즉 내가 생성자에 active를 넣지 않았다고 해도, 다음과 같이 Insert문을 보면 user에 active가 같이 들어가게 된다.

Hibernate: insert into user (active, name) values (?, ?)

 


그러면 null이 있어도 null을 안 날리게 하려면 어떻게 해결해줘야 하나?
 

방법 1. @PrePersist


JPA 엔티티 라이프 사이클의 콜백을 조종하게 해주는 어노테이션
얘랑 세트인 친구들로는 @PrePersist, @PostPersist , @PreRemove 등이 있다.
 
내가 엔티티를 만들고 repository의 save메서드를 호출하면, @PrePersist가 달린 메서드들이 호출이 되고 DB에 insert 된 후 @PostPersist 메서드가 호출이 된다.

@Entity
@Getter
@RequiredArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    
    @PrePersist
    public void setting(){
        this.name = "test";
        System.out.println(id);
    }
    
    @PostPersist
    public void printId(){
        System.out.println(id);
    }

 
다음과 같은 UserEntity에 
 
@PrePersist : name에 test를 넣어주고, id를 출력해 본다
 
@PostPersist : id를 출력해 본다
결과 값은 다음과 같다

==========
null
Hibernate: insert into user (active, name) values (?, ?)
3

 

방법 2.@DynamicInsert
 

Dynamic Insert는 쿼리가 날아갈 때 null인 구문을 빼고, 쿼리를 날려준다
바로 우리가 찾던 기능이다!

우리가 name필드에 값을 넣지 않더라도

Hibernate: insert into user (active) values (?)


null 쿼리조차 쏘지 않는다.
사용법도 클래스에 @DynamicInsert 어노테이션만 붙여주면 된다.



그러면 어떤 걸 쓰는 게 나을까?

애플리케이션에 따라 다르겠지만, 우선 default 값을 설정할 필드가 많으면 PrePersist를 위해 추가적으로 짜주어야 할 메서드가 많았다. 이에 반해 @DynamicInsert는 어노테이션 한번이명 된다

또한 default와 같은 값을 DB가 충분히 관리할 수 있는데 JPA가 미리 조작하게 하는 건 무언가 너무 JPA에게 의존하게 되는 것 같다.

그래서 나는 테이블 생성은 @columndefault로 default를 넣거나 혹은 직접 DB에서 default 값은 쿼리를 짜서 관리하고, 이후 객체가 주입될 때는  @DynamicInsert로 default 관리를 하기로 했다

Jpa는 정말 하나를 모르면, 다 모르는다는 말이 맞는 말인 것 같다


 

728x90