Hibernate @NotNull vs @Column (nullable = false)

1. Introduzione

A prima vista, può sembrare che le annotazioni @NotNull e @Column (nullable = false) abbiano lo stesso scopo e possano essere utilizzate in modo intercambiabile. Tuttavia, come vedremo presto, questo non è del tutto vero.

Anche se, se utilizzati sull'entità JPA, entrambi impediscono essenzialmente la memorizzazione di valori nulli nel database sottostante, esistono differenze significative tra questi due approcci.

In questo breve tutorial, confronteremo i vincoli @NotNull e @Column (nullable = false) .

2. Dipendenze

Per tutti gli esempi presentati, useremo una semplice applicazione Spring Boot.

Ecco una sezione pertinente del file pom.xml che mostra le dipendenze necessarie:

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

2.1. Entità campione

Definiamo anche un'entità molto semplice che useremo in questo tutorial:

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

3. L' annotazione @NotNull

L' annotazione @NotNull è definita nella specifica Bean Validation . Ciò significa che il suo utilizzo non è limitato solo alle entità. Al contrario, possiamo usare @NotNull anche su qualsiasi altro bean.

Il bastone di Let con il nostro caso d'uso e anche se aggiungere il @NotNull annotazioni alla voce 's prezzo campo:

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

Ora proviamo a persistere un articolo con un prezzo nullo :

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

E vediamo l'output di 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}"}]

Come possiamo vedere, in questo caso, il nostro sistema ha lanciato javax.validation.ConstraintViolationException .

È importante notare che Hibernate non ha attivato l'istruzione di inserimento SQL. Di conseguenza, i dati non validi non sono stati salvati nel database.

Questo perché l'evento del ciclo di vita dell'entità pre-persistente ha attivato la convalida del bean appena prima di inviare la query al database.

3.1. Generazione di schemi

Nella sezione precedente, abbiamo presentato come funziona la convalida @NotNull .

Scopriamo ora cosa succede se lasciamo che Hibernate generi lo schema del database per noi .

Per questo motivo, imposteremo un paio di proprietà nel nostro file application.properties :

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

Se ora avviamo la nostra applicazione, vedremo l'istruzione DDL:

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

Sorprendentemente, Hibernate aggiunge automaticamente il vincolo non nullo alla definizione della colonna del prezzo .

Come è possibile?

A quanto pare, fuori dagli schemi, Hibernate traduce le annotazioni di convalida del bean applicate alle entità nei metadati dello schema DDL.

Questo è abbastanza comodo e ha molto senso. Se applichiamo @NotNull all'entità, molto probabilmente vogliamo rendere anche la colonna del database corrispondente non nulla .

Tuttavia, se, per qualsiasi motivo, vogliamo disabilitare questa funzione Hibernate, tutto ciò che dobbiamo fare è impostare la proprietà hibernate.validator.apply_to_ddl su false.

Per verificarlo, aggiorniamo la nostra application.properties :

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

Eseguiamo l'applicazione e vediamo l'istruzione DDL:

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

Come previsto, questa volta Hibernate non ha aggiunto il vincolo non nullo alla colonna del prezzo .

4. Il @Column (nullable = false) annotazione

L' annotazione @Column è definita come parte della specifica API Java Persistence .

Viene utilizzato principalmente nella generazione di metadati dello schema DDL. Ciò significa che se lasciamo che Hibernate generi automaticamente lo schema del database, applica il vincolo non nullo alla particolare colonna del database .

Aggiorniamo la nostra entità Item con @Column (nullable = false) e vediamo come funziona in azione:

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

Possiamo ora provare a mantenere un valore di prezzo nullo :

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

Ecco lo snippet dell'output di 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"

Innanzitutto, possiamo notare che Hibernate ha generato la colonna del prezzo con il vincolo non nullo come avevamo anticipato .

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.

Come al solito, tutti gli esempi di codice sono disponibili su GitHub.