개발/JPA

[JPA] Column에 default Value 설정하기

Jindory 2022. 7. 2. 13:31
반응형

안녕하세요. 개발자 Jindory입니다.

오늘은 JPA의 칼럼값에 값이 설정되지 않았을때 default value로 저장될 수 있도록 설정하는 방법에 대해서 작성해보고자 합니다.

 

# 글 작성 이유

사용자가 값을 입력하지 않더라도 default로 값이 저장되길 원했으나, 예상과 다르게 null로 저장되거나 예샹치 못한 값으로 저장되는 문제가 발생하여, default value 설정을 어떤 방법으로 해야하는지를 정리하기 위해 작성해 보았습니다.

 


# Entity 및 Repository 구성

Account Entity

  • Account Entity에는 아래와 같은 항목들을 관리합니다.
    • id(PK,String,Not null,30) 
    • password(String,Not null,50)
    • displayName(String,20)
    • age(int)
    • privateYn(char)
  • 특정값 없이도 Entity가 생성되도록 아래와 같은 생성자 메서드를 만들었습니다.
    • public Account(String email,String pw,String displayName,int age) : privateYn 제외
    • public Account(String email,String pw,String displayName) : age,privateYn 제외
    • public Account(String email,String pw) : displayName,age,privateYn 제외
package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.ColumnDefault;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Table(name="ACCOUNT")
@NoArgsConstructor
public class Account {
	
	@Id
    @Column(nullable = false, length = 30)
	private String email;
	
	@Column(name = "PASSWORD", nullable = false, length = 50)
	private String pw;
	
	@Column(length = 20)
	@ColumnDefault("'guest'")
	private String displayName;
	
	@Column(length = 3)
	@ColumnDefault("'15'")
	private int age;
	
	@Column(length = 1)
	@ColumnDefault("'N'")
	private char privateYn;
	
	public Account(String email,String pw,String displayName,int age,char privateYn) {
		this.email = email;
		this.pw = pw;
		this.displayName = displayName;
		this.age = age;
		this.privateYn = privateYn;
	}
	
	public Account(String email,String pw,String displayName,int age) {
		this.email = email;
		this.pw = pw;
		this.displayName = displayName;
		this.age = age;
	}
	
	public Account(String email,String pw,String displayName) {
		this.email = email;
		this.pw = pw;
		this.displayName = displayName;
	}
	
	public Account(String email,String pw) {
		this.email = email;
		this.pw = pw;
	}
	
}

 

AccountRepository

JpaRepository를 상속받아서 기본 JPA기능을 사용할 수 있도록 만들었습니다.

package jpabook.jpashop.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import jpabook.jpashop.domain.Account;

public interface AccountRepository extends JpaRepository<Account,String> {

}

# Default value 설정 방법 확인을 위한 테스트 방법

먼저 column값을 default로 설정할 수 있는 방법을 아래와 같이 시도하며, 어떤 방법이 default value가 설정되는지 또 어떤 방법이 더 좋은지에 대해서 알아보겠습니다.

  • ColumnDefault로 설정
  • ColumnDefault로 설정 + @DynamicInsert,@DynamicUpdate
  • ColumnDefault로 설정 + @PrePersiste

TestCase 작성

  • Repository를 필드주입하고, Database에서 입력된 값들을 확인하기 위해 Rollback(false) 설정을 했습니다.
package jpabook.jpashop.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

import jpabook.jpashop.domain.Account;

@DataJpaTest
@Transactional
@AutoConfigureTestDatabase(replace = Replace.NONE)
public class AccountRepositoryUnitTest {

	@Autowired
	private AccountRepository accRepository;
	
	@Test
	@Rollback(false)
	void createAccount(){
		Account acc1 = new Account("temp1@google.com","123qwe","unknown",5);
		Account acc2 = new Account("temp2@google.com","123qwe","unknown");
		Account acc3 = new Account("temp3@google.com","123qwe");
		accRepository.save(acc1);
		accRepository.save(acc2);
		accRepository.save(acc3);
	}
	
}

1. ColumnDefault로 설정

ColumnDefault 어노테이션은 Table의 칼럼 생성시 default값이 설정되도록 하는 설정입니다.

String, int, char 자료형에 대해서 값이 매핑되지 않았을때 어떤 값들이 매핑되는지 알아보기 위해 아래의 3개의 값에 @ColumnDefault값을 각각 설정합니다.

@Column(length = 20)
@ColumnDefault("'guest'")
private String displayName;

@Column(length = 3)
@ColumnDefault("'15'")
private int age;

@Column(length = 1)
@ColumnDefault("'N'")
private char privateYn;

※ @ColumnDefault가 아닌 @Column에 columnDefinition으로도 Default값을 설정할 수 있습니다.

@Column(length = 20,columnDefinition =" varchar(255) default 'guest'")
private String displayName;
	
@Column(length = 3,columnDefinition ="integer default 15")	
private int age;
	
@Column(length = 1,columnDefinition ="char(1) default 'N'")
private char privateYn;

@ColumnDefault를 설정하면 JPA에서 테이블을 생성할때 아래와 같이 default값이 설정됩니다.

create table account (
       email varchar(255) not null,
        age integer default '15',
        display_name varchar(20) default 'guest',
        private_yn char(1) default 'N',
        password varchar(50) not null,
        primary key (email)
)

그렇다면 데이터를 저장할때 각각의 값이 설정되지 않으면 default값으로 설정 될까요?

위에서 작성한 Test Code를 실행했을때 아래와 같은 쿼리가 생성됩니다.

TestCode에서 실행한 Save  method에서는 생성하지 않은 displayName,age,privateYn이

null,0,' '으로 입력되는 것을 볼 수 있습니다. 

 

실제 데이터베이스에서도 DISPLAY_YN,AGE,PRIVATE_YN값이 기본값으로 들어가는것을 알 수 있습니다....

과연 설정하지 않은 값들이 왜 들어가는 걸까요?


Java의 자료형에서 값이 할당되지 않았을때, 아래와 같은 기본값이 들어가는 것을 알 수 있습니다.
String : null
byte : 0
short : 0
int : 0
long : 0L
float : 0.0f
double : 0.0d
char : '\u0000'(십진수로 0)
boolean : false

그래서 생성자에서 할당하지 않은 값이 기본값으로 생성된것을 알 수 있습니다.


2. ColumnDefault로 설정 + @DynamicInsert,@DynamicUpdate

@DynamicInsert와 @DynamicUpdate는 해당값이 입력되지 않았을때(null), INSERT, UPDATE에서 null인 value를 제외하는 어노테이션입니다.

@Getter
@Entity
@DynamicInsert
@DynamicUpdate
@NoArgsConstructor
public class Account {
						...중략...
    	@Column(length = 20)
	@ColumnDefault("'guest'")
	private String displayName;
	
	@Column(length = 3)
	@ColumnDefault("'15'")
	private int age;
	
	@Column(length = 1)
	@ColumnDefault("'N'")
	private char privateYn;
						...중략...
}

@ColumnDefault 설정을 했기 때문에 테이블 생성시 default값이 설정되는것을 알 수 있습니다.

create table account (
       email varchar(255) not null,
        age integer default '15',
        display_name varchar(20) default 'guest',
        private_yn char(1) default 'N',
        password varchar(50) not null,
        primary key (email)
    )

그렇다면 @DynamicInsert와 @DynamicUpdate의 설정으로 값이 매핑되지 않았을때 @ColumnDefault값으로 생성되었을까요?

위에 SQL문 생성된걸을 보면, privateYn과 age값은 제외되지 않았지만, displayName은 Insert문에 없는것을 알 수 있습니다.

Database에서 확인해 봤을때도, DISPLAY_NAME의 값은 default 값으로 생성된것을 알 수 있습니다.

 

왜 privateYn과 age값은 값을 입력하지 않았는데 insert문에 포함되고, displayName은 insert문에 없는것일까요?

@DynamicInsert
The @DynamicInsert annotation is used to specify that the INSERT SQL statement should be generated whenever an entity is to be persisted.
By default, Hibernate uses a cached INSERT statement that sets all table columns. When the entity is annotated with the @DynamicInsert annotation, the PreparedStatement is going to include only the non-null columns.
See the @CreationTimestamp mapping section for more info on how @DynamicInsert works.

@DynamicUpdate
The @DynamicUpdate annotation is used to specify that the UPDATE SQL statement should be generated whenever an entity is modified.
By default, Hibernate uses a cached UPDATE statement that sets all table columns. When the entity is annotated with the @DynamicUpdate annotation, the PreparedStatement is going to include only the columns whose values have been changed.

위 설명에 따르면 @DynamicInsert, @DynamicUpdate는 null이 아닌 값만 SQL문에 추가된다는 설명이며, privateYn(char), age(int)는 기본값이 null이 아니므로 Insert문에 그대로 추가됨을 알 수 있습니다.

 

자세한 설명은 아래의 url에서 찾아서 읽어보시면 도움이 될것 같습니다.

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


3. ColumnDefault로 설정 + @PrePersiste

@PrePersist는 Entity 객체가 저장되기 전에 특정 로직이 적용될 수 있도록 설정하는 어노테이션이며, persist 직전에 적용됩니다.

@Getter
@Entity
@Table(name="ACCOUNT")
@NoArgsConstructor
public class Account {
    								...중략..	
	@Column(length = 20)
	@ColumnDefault("'guest'")
	private String displayName;
	
	@Column(length = 3)
	@ColumnDefault("'15'")
	private int age;
	
	@Column(length = 1)
	@ColumnDefault("'N'")
	private char privateYn;
	
	@PrePersist
	public void prePersist() {
		// displayName이 null이면 default값으로 매핑 되도록 설정
		if(this.displayName == null) {
			this.displayName = "guest";
		}
		else {
			this.displayName = this.displayName;
		}
		
		// age이 0이면 default값으로 매핑 되도록 설정
		if(this.age == 0) {
			this.age = 15;
		}
		else {
			this.age = this.age;
		}
		
		// privateYn이 '\u0000'이면 default값으로 매핑 되도록 설정
		if(this.privateYn == '\u0000') {
			this.privateYn = 'N';
		}
		else {
			this.privateYn = this.privateYn;
		}
	}
    							...중략..	
}

@ColumnDefault 설정을 했기 때문에 테이블 생성시 default값이 설정되는것을 알 수 있다.

create table account (
       email varchar(255) not null,
        age integer default '15',
        display_name varchar(20) default 'guest',
        private_yn char(1) default 'N',
        password varchar(50) not null,
        primary key (email)
    )

그렇다면 @PrePersist의 설정으로 값이 매핑되지 않았을때 설정한 로직으로 생성되었을까요?

위와 같이 @PrePersist에서 설정한 값으로 값들이 매핑되어 Insert된것을 볼 수 있습니다!!!

Database에도 저희가 @PrePersist에 설정한 값으로 저장되어 있네요.


※ 하지만 @ColumnDefault값과 @PrePersist에서 설정한 값이 달라질때는 @PrePersist의 설정이 먼저 적용되어 SQL에 반영되고 @DynamicInsert나 @DynamicUpdate가 없으면 @CollumnDefault 어노테이션은 제대로 적용되지 않으므로, 개발자로 하여금 더 큰 혼란을 야기할 수 있습니다.

 

< @ColumnDefault에서 설정한 값과 @PrePersist에서 설정한 값을 다르게 했을경우 >

	@Column(length = 20)
	@ColumnDefault("'guest'")
	private String displayName;
	
	@Column(length = 3)
	@ColumnDefault("'15'")
	private int age;
	
	@Column(length = 1)
	@ColumnDefault("'N'")
	private char privateYn;
	
	@PrePersist
	public void prePersist() {
		// displayName이 null이면 default값으로 매핑 되도록 설정
		if(this.displayName == null) {
			this.displayName = "member";
		}
		else {
			this.displayName = this.displayName;
		}
		
		// age이 0이면 default값으로 매핑 되도록 설정
		if(this.age == 0) {
			this.age = 999;
		}
		else {
			this.age = this.age;
		}
		
		// privateYn이 '\u0000'이면 default값으로 매핑 되도록 설정
		if(this.privateYn == '\u0000') {
			this.privateYn = 'F';
		}
		else {
			this.privateYn = this.privateYn;
		}
	}

Table을 생성시에는 @ColumnDefault에 따라서 default값이 설정되는것으로 보입니다.

create table account (
       email varchar(255) not null,
        age integer default '15',
        display_name varchar(20) default 'guest',
        private_yn char(1) default 'N',
        password varchar(50) not null,
        primary key (email)
    )

하지만 TestCode가 실행된 SQL을 보면 @PrePersist에서 설정한 값들이 먼저 적용되어, Table에서 설정한 default값과 다른 값들로 설정되어 Entity가 생성됨을 알 수 있습니다.

Database를 확인해 봐도, @PrePersist에 설정한 로직이 반영되어 결과를 본 개발자가 큰 혼란을 가질 수 있습니다!!!

그래서 제가 생각했을때,

만일 @ColumnDefault를 설정한 자료형이 모두 String값이라고 한다면, @ColumnDefault + @DynamicInsert,@DynamicUpdate 조합으로 설정하여 코드를 간결하게 하는것도 좋다고 생각합니다.

 

만일 Default로 설정해야하는 값이 String이 아닌 int,char,boolean,long과 같이 기본값이 null이 아닌 값이 있거나, 특정한 값에 따라서 default값이 설정되어야 할 경우에는 @PrePersist만 설정하여, 영속성 상태가 되기 이전에 값을 셋팅하여 SQL이 생성되도록 설정하는것이 더 좋다고 생각합니다.


이렇게 JPA의 칼럼값에 값이 설정되지 않았을때 default value로 저장될 수 있도록 설정하는 방법에 대해서 알아봤습니다.

 

혹시라도 정정할 내용이나 추가적으로 필요하신 정보가 있다면 댓글 남겨주시면 감사하겠습니다.

오늘도 Jindory 블로그에 방문해주셔서 감사합니다.

 

[참고]

http://daplus.net/java-int%EA%B0%80-null%EC%9D%B8%EC%A7%80-%ED%99%95%EC%9D%B8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95/

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

https://www.baeldung.com/jpa-entity-lifecycle-events

 

Hibernate ORM 5.3.27.Final User Guide

Fetching, essentially, is the process of grabbing data from the database and making it available to the application. Tuning how an application does fetching is one of the biggest factors in determining how an application will perform. Fetching too much dat

docs.jboss.org

 

반응형