Hibernate @NotNull vs @Column(nullable = false)

Dev.Hammy·2023년 7월 1일
0

JPA_Hibernate

목록 보기
11/14

예시에서 스프링 부트 애플리케이션을 사용할 것이다.

Dependencies

예시) 의존성을 정의하는 pom.xml 파일

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>

예시) 샘플 엔터티

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private BigDecimal price;
}

The @NotNull Annotation

@NotNull은 Bean Validation 사양에 정의되어 있으며 엔터티에 한정하지 않고 활용할 수 있다. 어떤 빈에서라도 @NotNull을 사용할 수 있다.

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    private BigDecimal price;
}

예시) null 가격을 item에 persist 하는 것을 시도

@SpringBootTest
public class ItemIntegrationTest {

    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void shouldNotAllowToPersistNullItemsPrice() {
        itemRepository.save(new Item());
    }
}

@Autowired를 사용하면 스프링은 해당 필드를 자동으로 적절한 빈으로 주입해줍니다. 이를 통해 개발자는 불필요한 초기화나 레퍼런스 관리 등을 신경쓰지 않고 의존성을 주입받아 사용할 수 있게 됩니다.

코드에서 itemRepository.save(new Item());를 호출하면 새로운 Item 객체가 데이터베이스에 저장되려고 시도되는데, 이때 price 필드가 null인 상태로 저장하려는 시도입니다.

이에 대해 Hibernate는 javax.validation.ConstraintViolationException을 발생시킨다.
Hiberate가 SQL insert문을 트리거하지 않았다는 것에 주목
Pre-Persist lifecycle 이벤트가 데이터베이스에 쿼리를 전송하기 전에 빈 검증(bean validation)을 트리거 했기 때문이다.

스키마 생성

Hibernate가 데이터베이스에 스키마를 생성한다면 어떤 일이 일어날까
예시) application.properties 파일에 프로퍼티 추가

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

애플리케이션을 실행하면 DDL 명령문이 실행되는 것을 확인할 수 있다.

create table item (
   id bigint not null,
    price decimal(19,2) not null,
    primary key (id)
)

Hibernate는 not null 제약을 price 열 정의에 추가했다.
Hibernate가 엔터티에 적용된 bean validation을 DDL 스키마 메타데이터로 번역해주었기 때문이다.
Hibernate의 이런 특성을 비활성화 하려면 아래와 같이 application.properties를 변경한다

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.validator.apply_to_ddl=false

애플리케이션을 다시 실행하면 not null 제약이 적용되지 않은 DDL이 보인다

create table item (
   id bigint not null,
    price decimal(19,2),
    primary key (id)
)

The @Column(nullable = false) Annotation

@Column은 JPA 사양 일부로 정의되었으며 DDL 스키마 메타데이터를 생성하는데 사용된다. 이는 Hibernate가 데이터베이스 스키마를 자동 생성하면, not null 제약이 특정 데이터베이스 컬럼에 적용한다는 말이다.

예시) Item 엔터티에 @Column(nullable = false) 적용

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private BigDecimal price;
}

null price 값을 persist 시도하기

@SpringBootTest
public class ItemIntegrationTest {

    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void shouldNotAllowToPersistNullItemsPrice() {
        itemRepository.save(new Item());
    }
}

아래는 Hibernate의 출력

Hibernate: 
    
    create table item (
       id bigint not null,
        price decimal(19,2) not null,
        primary key (id)
    )

(...)

Hibernate: 
    insert 
    into
        item
        (price, id) 
    values
        (?, ?)
2019-11-14 13:23:03.000  WARN 14580 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper   : 
SQL Error: 23502, SQLState: 23502
2019-11-14 13:23:03.000 ERROR 14580 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper   : 
NULL not allowed for column "PRICE"

예상대로 not null 제약을 price 열에 적용하여 생성하였다. 그리고 SQL INSERT 쿼리를 실행하여 기반 데이터베이스에서 에러를 발생시켰다.

Validation

Hibernate는 상응하는 필드가 @Column(nullable=false)로 annotated 되었더라도 null 값 가능성에 대한 엔터티 validation을 수행할 수 있다.
이를 활성화 시키기 위해 명시적으로 hibernate.check_nullability 프로퍼티를 true로 설정한다.

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.check_nullability=true

테스트 실행

org.springframework.dao.DataIntegrityViolationException: 
not-null property references a null or transient value : com.baeldung.h2db.springboot.models.Item.price; 
nested exception is org.hibernate.PropertyValueException: 
not-null property references a null or transient value : com.baeldung.h2db.springboot.models.Item.price

이번에는 테스트케이스에서 org.hibernate.PropertyValueException을 발생시켰다. 이 케이스에서는 Hibernate가 데이터베이스에 INSERT 쿼리를 전송하지 않은 점에 주목.

0개의 댓글