Hibernate@NotNullvs@Column(nullable=false)
1. 序章
一見すると、 @NotNullと@Column(nullable = false)の両方のアノテーションが同じ目的を果たし、互換的に使用できるように見えるかもしれません。 ただし、すぐにわかるように、これは完全に正しいわけではありません。
JPAエンティティで使用すると、どちらも基本的に、基になるデータベースにnull値を格納できなくなりますが、これら2つのアプローチには大きな違いがあります。
このクイックチュートリアルでは、 @NotNull制約と@Column(nullable = false)制約を比較します。
2. 依存関係
提示されたすべての例では、単純なSpring Bootアプリケーションを使用します。
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>
2.1. サンプルエンティティ
また、このチュートリアル全体で使用する非常に単純なエンティティを定義しましょう。
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
private BigDecimal price;
}
3. @NotNullアノテーション
@NotNullアノテーションは、BeanValidation仕様で定義されています。 これは、その使用法がエンティティだけに限定されないことを意味します。 それどころか、他のBeanでも@NotNullを使用できます。
ただし、ユースケースに固執し、@NotNullアノテーションをItemのpriceフィールドに追加しましょう。
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
@NotNull
private BigDecimal price;
}
それでは、 null priceでアイテムを永続化してみましょう。
@SpringBootTest
public class ItemIntegrationTest {
@Autowired
private ItemRepository itemRepository;
@Test
public void shouldNotAllowToPersistNullItemsPrice() {
itemRepository.save(new Item());
}
}
そして、Hibernateの出力を見てみましょう:
2019-11-14 12:31:15.070 ERROR 10980 --- [ main] o.h.i.ExceptionMapperStandardImpl :
HHH000346: Error during managed flush [Validation failed for classes
[com.baeldung.h2db.springboot.models.Item] during persist time for groups
[javax.validation.groups.Default,] List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, rootBeanClass=class
com.baeldung.h2db.springboot.models.Item,
messageTemplate='{javax.validation.constraints.NotNull.message}'}]]
(...)
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes
[com.baeldung.h2db.springboot.models.Item] during persist time for groups
[javax.validation.groups.Default,] List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, rootBeanClass=class
com.baeldung.h2db.springboot.models.Item,
messageTemplate='{javax.validation.constraints.NotNull.message}'}]
ご覧のとおり、この場合、システムはjavax.validation.ConstraintViolationException。をスローしました。
HibernateがSQL挿入ステートメントをトリガーしなかったことに注意することが重要です。 その結果、無効なデータはデータベースに保存されませんでした。
これは、pre-persistエンティティライフサイクルイベントが、クエリをデータベースに送信する直前にBean検証をトリガーしたためです。
3.1. スキーマの生成
前のセクションでは、@NotNull検証がどのように機能するかを示しました。
ここで、weがHibernateにusのデータベーススキーマを生成させた場合に何が起こるかを調べてみましょう。
そのため、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は価格列の定義にnull以外の制約を自動的に追加します。
どうしてそれが可能ですか?
実は、箱から出して、Hibernateはエンティティに適用されたBean検証アノテーションをDDLスキーマメタデータに変換します。
これは非常に便利で、非常に理にかなっています。 @NotNull をエンティティに適用する場合、対応するデータベース列もnullではないようにする必要があります。
ただし、何らかの理由ででこのHibernate機能を無効にしたい場合は、hibernate.validator.apply_to_ddlプロパティをfalseに設定するだけです。
これをテストするために、application.propertiesを更新しましょう。
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
アプリケーションを実行して、DDLステートメントを見てみましょう。
create table item (
id bigint not null,
price decimal(19,2),
primary key (id)
)
予想どおり、今回、Hibernateは価格列にnull以外の制約を追加しませんでした。
4. @Column(nullable = false)アノテーション
@Columnアノテーションは、JavaPersistenceAPI仕様の一部として定義されています。
これは主にDDLスキーマメタデータの生成で使用されます。 これは、 Hibernateにデータベーススキーマを自動的に生成させる場合、特定のデータベース列に非ヌル制約を適用することを意味します。
アイテムエンティティを@Column(nullable = false)で更新して、これが実際にどのように機能するかを見てみましょう。
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private BigDecimal price;
}
これで、 nullprice値を永続化することができます。
@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"
まず、 Hibernateが、予想どおりでないnull制約を使用して価格列を生成したことがわかります。
さらに、SQL挿入クエリを作成してパススルーすることができました。 その結果、エラーを引き起こしたのは基盤となるデータベースです。
4.1. 検証
ほとんどすべてのソースは、 @Column(nullable = false)がスキーマDDL生成にのみ使用されることを強調しています。
ただし、Hibernateは、対応するフィールドに@Column(nullable = false)の注釈が付けられている場合でも、可能なnull値に対してエンティティの検証を実行できます。
このHibernate機能をアクティブにするには、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がSQL挿入クエリをデータベースに送信しなかったことに注意することが重要です。
5. 概要
この記事では、 @NotNullおよび@Column(nullable – false)アノテーションがどのように機能するかについて説明しました。
どちらもnull値をデータベースに保存することを妨げていますが、異なるアプローチを採用しています。
経験則として、@ Column(nullable = false)アノテーションよりも@NotNullアノテーションを優先する必要があります。 このようにして、Hibernateが挿入または更新SQLクエリをデータベースに送信する前に検証が行われることを確認します。
また、通常は、データベースに検証ロジックを処理させるよりも、Bean検証で定義された標準ルールに依存する方が優れています。
ただし、Hibernateにデータベーススキーマを生成させたとしても、
いつものように、すべてのコード例はGitHubでから入手できます。