Hibernate @NotNull vs @Column (nullable = false)

1. Introduction

À première vue, il peut sembler que les annotations @NotNull et @Column (nullable = false) ont le même objectif et peuvent être utilisées de manière interchangeable. Cependant, comme nous le verrons bientôt, ce n'est pas tout à fait vrai.

Même si, lorsqu'ils sont utilisés sur l'entité JPA, les deux empêchent essentiellement de stocker des valeurs nulles dans la base de données sous-jacente, il existe des différences significatives entre ces deux approches.

Dans ce rapide tutoriel, nous comparerons les contraintes @NotNull et @Column (nullable = false) .

2. Dépendances

Pour tous les exemples présentés, nous utiliserons une simple application Spring Boot.

Voici une section pertinente du fichier pom.xml qui montre les dépendances nécessaires:

  org.springframework.boot spring-boot-starter-data-jpa   org.springframework.boot spring-boot-starter-validation   com.h2database h2  

2.1. Entité échantillon

Définissons également une entité très simple que nous utiliserons tout au long de ce tutoriel:

@Entity public class Item { @Id @GeneratedValue private Long id; private BigDecimal price; }

3. L' annotation @NotNull

L' annotation @NotNull est définie dans la spécification Bean Validation . Cela signifie que son utilisation n'est pas limitée uniquement aux entités. Au contraire, nous pouvons également utiliser @NotNull sur n'importe quel autre bean.

Bâton de Let avec notre cas d'utilisation bien et ajoutez la @NotNull annotation à l' article « s Prix terrain:

@Entity public class Item { @Id @GeneratedValue private Long id; @NotNull private BigDecimal price; }

Maintenant, essayons de conserver un article avec un prix nul :

@SpringBootTest public class ItemIntegrationTest { @Autowired private ItemRepository itemRepository; @Test public void shouldNotAllowToPersistNullItemsPrice() { itemRepository.save(new Item()); } }

Et voyons la sortie d'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}"}]

Comme nous pouvons le voir, dans ce cas, notre système a lancé javax.validation.ConstraintViolationException .

Il est important de noter qu'Hibernate n'a pas déclenché l'instruction d'insertion SQL. Par conséquent, les données non valides n'ont pas été enregistrées dans la base de données.

En effet, l'événement de cycle de vie de l'entité pré-persistance a déclenché la validation du bean juste avant l'envoi de la requête à la base de données.

3.1. Génération de schéma

Dans la section précédente, nous avons présenté le fonctionnement de la validation @NotNull .

Voyons maintenant ce qui se passe si nous laissons Hibernate générer le schéma de base de données pour nous .

Pour cette raison, nous définirons quelques propriétés dans notre fichier application.properties :

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

Si nous démarrons maintenant notre application, nous verrons l'instruction DDL:

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

Étonnamment, Hibernate ajoute automatiquement la contrainte non nulle à la définition de la colonne de prix .

Comment est-ce possible?

En fin de compte, Hibernate traduit les annotations de validation de bean appliquées aux entités dans les métadonnées du schéma DDL.

Ceci est assez pratique et a beaucoup de sens. Si nous appliquons @NotNull à l'entité, nous souhaitons très probablement rendre la colonne de base de données correspondante non nulle également.

Cependant, si, pour une raison quelconque, nous voulons désactiver cette fonctionnalité Hibernate, tout ce que nous devons faire est de définir la propriété hibernate.validator.apply_to_ddl sur false.

Afin de tester cela, mettons à jour notre application.properties :

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

Exécutons l'application et voyons l'instruction DDL:

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

Comme prévu, cette fois Hibernate n'a pas ajouté la contrainte non nulle à la colonne de prix .

4. Le @Column (annulable = false) Annotation

L' annotation @Column est définie comme faisant partie de la spécification de l'API Java Persistence .

Il est principalement utilisé dans la génération de métadonnées de schéma DDL. Cela signifie que si nous laissons Hibernate générer automatiquement le schéma de base de données, il applique la contrainte non nulle à la colonne de base de données particulière .

Mettons à jour notre entité Item avec la @Column (nullable = false) et voyons comment cela fonctionne en action:

@Entity public class Item { @Id @GeneratedValue private Long id; @Column(nullable = false) private BigDecimal price; }

Nous pouvons maintenant essayer de conserver une valeur de prix nulle :

@SpringBootTest public class ItemIntegrationTest { @Autowired private ItemRepository itemRepository; @Test public void shouldNotAllowToPersistNullItemsPrice() { itemRepository.save(new Item()); } }

Voici l'extrait de la sortie d'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"

Tout d'abord, nous pouvons remarquer qu'Hibernate a généré la colonne de prix avec la contrainte non nulle comme nous l'avions prévu .

Additionally, it was able to create the SQL insert query and pass it through. As a result, it's the underlying database that triggered the error.

4.1. Validation

Almost all the sources emphasize that @Column(nullable = false) is used only for schema DDL generation.

Hibernate, however, is able to perform the validation of the entity against the possible null values, even if the corresponding field is annotated only with @Column(nullable = false).

In order to activate this Hibernate feature, we need to explicitly set the hibernate.check_nullability property to true:

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

Let's now execute our test case again and examine the output:

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

This time, our test case threw the org.hibernate.PropertyValueException.

It's crucial to notice that, in this case, Hibernate didn't send the insert SQL query to the database.

5. Summary

In this article, we've described how the @NotNull and @Column(nullable – false) annotations work.

Even though both of them prevent us from storing null values in the database, they take different approaches.

As a rule of thumb, we should prefer the @NotNull annotation over the @Column(nullable = false) annotation. This way, we make sure the validation takes place before Hibernate sends any insert or update SQL queries to the database.

Also, it's usually better to rely on the standard rules defined in the Bean Validation, rather than letting the database handle the validation logic.

But, even if we let Hibernate generate the database schema, it'll translate the @NotNull annotation into the database constraints. We must then only make sure that hibernate.validator.apply_to_ddl property is set to true.

Comme d'habitude, tous les exemples de code sont disponibles à l'adresse over sur GitHub.