Spring/JPA

@NotNull vs @Column(nullable = false), JPA에서 INSERT 전에 Null 검사하는 방법

ysk(0soo) 2022. 12. 9. 14:43
  • @Column(nullable=false) 옵션은 기본적으로는 INSERT 시 null값 삽입을 막아주지 못한다.

 

결론부터,

  • @Column(nullable = false) 만 필드에 사용하면, INSERT시에 Null 검사 를 하지 않는다
    • 이것은 기본 정책이며 바꿀 수 있다.
    • @Column(nullable = false) 만 사용해서 null값 방지를 하려면 spring.jpa.properties.hibernate.check_nullability=true 옵션 사용.
    • 아래를 참고하자.
  • Null검사를 하고 싶다면 @NotNull 어노테이션을 사용하여 @Column(nullable = false) 와 함께 정의하면, INSERT 전에 방지한다.

 

JPA @Column(nullable = false)

JPA 에서 Entity 저장시에 데이터베이스에 null 값을 저장하는 것을 방지하기 위해서는 Validation이 필요하다.

그럴 때, 보통 @Column 어노테이션을 이용하여 @Column(nullable = false) 을 이용한다.

엔티티로 삼을 객체에 @Entity 어노테이션을 붙이고,

추가적으로 여러 매핑 정보를 엔티티의 필드 위에 추가하여, 자동 생성되는 DDL에 제약조건을 추가할 수 있다.

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

    @Column(nullable = false)
    private String isbn;
}
  • 다음과 같이 쿼리가 DDL SQL문이 실행되는것을 볼 수 있다.
create table book (
    id bigint generated by default as identity,
    isbn varchar(255) not null,
    primary key (id)
)

@Column(nullable = false)

  • nullable은 @Column의 속성 중 하나로, 기본값은 true이다.
  • 값을 false로 설정해 주면, 해당 필드는 DDL 생성 시 not null이라는 조건이 붙은 채로 생성된다.

하지만 데이터베이스 저장시에는 엔티티 필드 값이 Null이여도 유효성 검사를 해주지 않으며, 실제 DB로 쿼리가 날라간 후에 예외가 발생한다.

org.hibernate.PropertyValueException: not-null property references a null or transient value : jpabook.jpashop.domain.BookEntity

이 때, javax.validation.constraints@NotNull 어노테이션도 엔티티에 붙여 사용할 수 있다.

 

Java Bean Validation

  • 어노테이션 형태로 제약 조건을 달아줘서 쉽게 검증할 수 있도록 돕는 API이다.
  • Bean Validation은 인터페이스로 된 명세일 뿐이고 실제 동작할 수 있도록 구현한 것이 Hibernate Validator이다.
  • javax.validation 패키지를 사용하며 의존성을 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'

@NotNull @NotEmpty @NotBlank 등 다양한 어노테이션을 지원한다.

 

@NotNull과 @Column(nullable = false)의 차이

엔티티 필드의 null을 검증하기 위해서 대표적으로 사용되는 어노테이션이 @Column(nullable = false)이다.

  • @NotNull과는 차이가 있다.

언뜻 보기에 @NotNull\ @Column(nullable = false)\ 주석이 모두 동일한 목적 을 수행하고 서로 교환하여 사용할 수 있는 것처럼 보이지만, 그렇지 않다.

@Column(nullable = false)을 사용할 때와 마찬가지로 @NotNull 어노테이션 적용 시, 테이블 생성시 NOT NULL DDL이 입력된다. 이는 Hibernate가 @NotNull 어노테이션을 해석할 수 있기 때문이다. 만일 Hibernate가 해석하길 원하지 않는 경우 application.properties에 아래 옵션을 지정해주면 된다.

# application.properties
spring.jpa.properties.hibernate.validator.apply_to_ddl=false

 

@Column(nullable=false) 시에 @NotNull 없이, INSERT 방지하는 방법

  • 이 옵션은 바꿀 수 있다
  • Hibernate 는 해당 필드가 @Column(nullable = false)\ 로만 주석이 달린 경우에도 가능한 null\ 값 에 대해 엔티티의 유효성 검사를 수행할 수 있다.
  • https://www.baeldung.com/hibernate-notnull-vs-nullable#1-validation
  • 이 Hibernate 기능을 활성화하려면 명시적으로 hibernate.check_nullability 속성을 true 로 설정해야 한다
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.check_nullability=true
  • 이 경우 Hibernate가 삽입 SQL 쿼리를 데이터베이스에 보내지 않는다.
  • default 설정은 다음과 같다. (기본 정책)
    • Bean Validation이 클래스패스에 있고, Hibernate Annotation이 사용되면 기본 값은 false - 검사를 안 한다.
    • Bean Validation이 클래스패스에 없고 Hibernate Annotation을 사용 안하면 true 이다. - 검사를 한다
  • 이것도 베스트 프랙티스가 아니라며 @PrePersist, @PreUpdate 이런 것을 쓰면 더 좋다고 한다
  • 아마, 인설트나 업데이트 호출 전에 널 검사를 하는것 같다.
  • https://jeong-pro.tistory.com/233

 

그렇다면 @NotEmpty와 @NotBlank는?

Spring Bean Validation에서는 @NotNull 외에도, 이와 비슷한 어노테이션을 제공한다.

  • @NotNull : null이 들어갈 경우 예외를 발생시킨다.
  • @NotEmpty : 문자열에 null이나 ""가 들어가면 예외를 발생시킨다. NotNull의 문자열 버전 상위호환.
  • @NotBlank : 문자열에 null, "", " "가 들어가면 예외를 발생시킨다. NotEmpty의 상위호환.

위의 @NotEmpty와 @NotBlank는, 문자열에만 적용할 수 있는 검증 어노테이션이다.

단순하게 생각했을 때에는, @NotEmpty와 @NotBlank 도 not null 옵션을 자동으로 붙여줄것 같지만 그렇지 않다.

@NotBlank
Checks that the annotated … (생략)

Hibernate metadata impact
None


@NotEmpty
Checks whether the annotated element is not null nor empty

Hibernate metadata impact
None


@NotNull
Checks that the annotated value is not null

Hibernate metadata impact
Column(s) are not nullable

 

Hibernate 공식 문서

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}

위의 코드는 공식 페이지의 메인에서 보여주는 예시 코드이다.

다음과 같이 권하고 있다.

  • @NotBlank나 @NotEmpty를 통해 not null 옵션을 조절하는 대신,
  • @NotNull을 통해 DB에 not null 옵션을 주고 @Size를 통해 저장할 때 서버에서 추가로 검증을 하도록 한다.

일관성을 위해 @NotNull에 대해서만 not null 옵션으로 번역해 주는것 같다.

  • 빈 값은 null이 아니고, 빈 값은 저장될 수 있으니까?

 

엔티티에 검증 로직이 필요한 이유

단점은 명확하다. 어노테이션이 많아서 코드가 더러워 보인다.
하지만, 다음도 생각해 보아야 한다.

  • 의도치 않게 엔티티에 잘못된 값 (null 등)을 넣어도, Repository에 저장할 때 예외를 발생시켜 더 큰 문제를 막을 수 있다.
  • @NotNull 어노테이션을 통해 @Column의 nullable을 대체할 수 있다.
  • 다른 사람이 코드를 봤을 때, 어떤 제약 조건이 있는지 파악하기 쉽다.
  • 비즈니스 로직과 데이터 유효성 검사 로직 분리를 통해 애플리케이션을 깔끔하게 개발할 수 있게 해준다.

 

참조