Валидация
Некоторые данные вообще не должны попадать в базу: статья без заголовка, email без знака @. Jakarta Validation (Bean Validation) описывает такие правила аннотациями на полях. Hibernate находит валидатор в зависимостях и автоматически проверяет сущность перед insert и update. Эталонная реализация — отдельный проект Hibernate Validator.
Зависимости
Добавьте в pom.xml две зависимости:
<!-- pom.xml -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>9.1.4.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>6.0.0</version>
</dependency>Вторая библиотека собирает тексты ошибок из шаблонов. Настраивать ничего не нужно: Hibernate включает проверку, как только видит валидатор.
Правила на полях
// src/main/java/com/example/blog/Post.java
package com.example.blog;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Entity
public class Post {
@Id
@GeneratedValue
private Long id;
@NotBlank
@Size(max = 200)
private String title;
@Size(min = 10, message = "статья слишком короткая")
private String body;
@Email
private String authorEmail;
protected Post() {
}
public Post(String title, String body, String authorEmail) {
this.title = title;
this.body = body;
this.authorEmail = authorEmail;
}
}| Аннотация | Правило |
|---|---|
@NotNull | Не null |
@NotBlank | Строка хотя бы с одним непробельным символом |
@Size(min, max) | Длина строки или размер коллекции |
@Email | Похоже на адрес электронной почты |
@Min, @Max, @Positive | Ограничения чисел |
@Past, @Future | Даты |
Правила влияют и на таблицу: Hibernate создаёт title varchar(200) not null.
Автоматическая проверка
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> em.persist(new Post("Валидация", "Проверки перед сохранением", "anna@example.com")));
IO.println("Первая статья сохранена");
try {
emf.runInTransaction(em -> em.persist(new Post(" ", "Коротко", "anna-at-example.com")));
} catch (RollbackException e) {
if (e.getCause() instanceof ConstraintViolationException errors) {
for (ConstraintViolation<?> v : errors.getConstraintViolations()) {
IO.println(v.getPropertyPath() + ": " + v.getMessage());
}
}
}Проверка выполняется при записи изменений, поэтому ошибка приходит при фиксации, обёрнутая в RollbackException. Вывод (с русской локалью):
Hibernate: insert into Post (authorEmail,body,title,id) values (?,?,?,?)
Первая статья сохранена
Hibernate: select next value for Post_SEQ
body: статья слишком короткая
authorEmail: должно иметь формат адреса электронной почты
title: не должно быть пустымВторая статья в базу не попала: для неё нет insert. Порядок сообщений может отличаться.
Ручная проверка
В приложении данные обычно проверяют раньше — сразу после получения формы, — чтобы показать ошибки пользователю:
// src/main/java/com/example/blog/Check.java
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
Validator validator = factory.getValidator();
Post post = new Post("", "Коротко", "oleg@example.com");
Set<ConstraintViolation<Post>> errors = validator.validate(post);
IO.println("Ошибок: " + errors.size());
errors.forEach(v -> IO.println(v.getPropertyPath() + ": " + v.getMessage()));
}Ошибок: 2
title: не должно быть пустым
body: статья слишком короткаяСтандартные сообщения переведены: с русской локалью программа печатает русские тексты, с английской — must not be blank. В Spring Boot те же аннотации проверяет @Valid на параметре контроллера.
Частая ошибка
Ловить ConstraintViolationException вокруг persist бесполезно: persist только запоминает объект, а проверка происходит позже, при сбросе изменений или фиксации. Ловите RollbackException вокруг транзакции, как выше, или вызовите внутри try метод em.flush(), чтобы проверка выполнилась сразу.