Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Валидация

Некоторые данные вообще не должны попадать в базу: статья без заголовка, 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(), чтобы проверка выполнилась сразу.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.