예시에서 스프링 부트 애플리케이션을 사용할 것이다.
예시) 의존성을 정의하는 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;
}
@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)
)
@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 쿼리를 실행하여 기반 데이터베이스에서 에러를 발생시켰다.
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 쿼리를 전송하지 않은 점에 주목.