Автор оригинала: Krzysztof Woyke.
1. введение
На первый взгляд может показаться, что аннотации @NotNull и @Column(nullable) служат одной и той же цели и могут использоваться взаимозаменяемо. Однако, как мы скоро увидим, это не совсем так.
Несмотря на то, что при использовании в сущности JPA оба они по существу предотвращают хранение null значений в базовой базе данных, между этими двумя подходами существуют существенные различия.
В этом кратком руководстве мы сравним ограничения @NotNull и @Column(nullable) .
2. Зависимости
Для всех представленных примеров мы будем использовать простое приложение Spring Boot.
Вот соответствующий раздел pom.xml файл, который показывает необходимые зависимости:
org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-validation com.h2database h2
2.1. Образец объекта
Давайте также определим очень простую сущность, которую мы будем использовать на протяжении всего этого урока:
@Entity public class Item { @Id @GeneratedValue private Long id; private BigDecimal price; }
3. Аннотация @NotNull
Аннотация @NotNull определена в спецификации Bean Validation . Это означает, что его использование не ограничивается только сущностями. Напротив, мы можем использовать @NotNull и на любом другом бобе.
Однако давайте придерживаться нашего варианта использования и добавим аннотацию @NotNull в поле Item ‘s price :
@Entity public class Item { @Id @GeneratedValue private Long id; @NotNull private BigDecimal price; }
Теперь давайте попробуем сохранить элемент с null ценой :
@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 insert. Следовательно, недопустимые данные не были сохранены в базе данных.
Это связано с тем, что событие жизненного цикла сущности prepersist инициировало проверку компонента непосредственно перед отправкой запроса в базу данных.
3.1. Генерация схемы
В предыдущем разделе мы представили, как работает проверка @NotNull .
Давайте теперь выясним, что произойдет, если мы позволим 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 переводит аннотации проверки компонентов, примененные к сущностям, в метаданные схемы DDL.
Это довольно удобно и имеет большой смысл. Если мы применим @NotNull к сущности, мы, скорее всего, также захотим сделать соответствующий столбец базы данных not null .
Однако, если по какой-либо причине мы хотим отключить эту функцию гибернации, все, что нам нужно сделать, это установить для свойства 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 не добавила ограничение not null в столбец price /.
4.) Аннотация
Аннотация @Column определена как часть Java Persistence API specification .
Он используется в основном для генерации метаданных схемы DDL. Это означает, что если мы позволим Hibernate автоматически генерировать схему базы данных, она применит ограничение not null к конкретному столбцу базы данных .
Давайте обновим наш Элемент сущность со столбцом @(nullable) и посмотрите, как это работает в действии:
@Entity public class Item { @Id @GeneratedValue private Long id; @Column(nullable = false) private BigDecimal price; }
Теперь мы можем попытаться сохранить нулевую цену значение:
@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 сгенерировал столбец цены с ограничением not null , как мы и ожидали .
Кроме того, он смог создать запрос SQL insert и передать его. В результате это базовая база данных, которая вызвала ошибку.
4.1. Валидация
Почти все источники подчеркивают, что @Column(nullable) используется только для генерации DDL схемы.
Однако Hibernate может выполнить проверку сущности на соответствие возможным значениям null , даже если соответствующее поле аннотировано только с помощью @Column(nullable) .
Чтобы активировать эту функцию гибернации, нам нужно явно установить для свойства 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 SQL в базу данных .
5. Резюме
В этой статье мы описали, как работают @NotNull и @Column(nullable – false) аннотации.
Несмотря на то, что оба они мешают нам хранить значения null в базе данных, они используют разные подходы.
Как правило, , мы должны предпочесть аннотацию @NotNull аннотации колонке @(nullable) аннотации//. Таким образом, мы удостоверяемся, что проверка выполняется до того, как Hibernate отправит любые запросы SQL на вставку или обновление в базу данных.
Кроме того, обычно лучше полагаться на стандартные правила , определенные в проверке бобов , а не позволять базе данных обрабатывать логику проверки.
Но даже если мы позволим Hibernate сгенерировать схему базы данных, она переведет аннотацию @NotNull в ограничения базы данных. Затем мы должны только убедиться, что свойство hibernate.validator.apply_to_ddl имеет значение true.
Как обычно, все примеры кода доступны на GitHub .