Перейти к основному содержимому

События жизненного цикла сущности JPA

· 5 мин. чтения

1. Введение

При работе с JPA есть несколько событий, о которых мы можем получать уведомления в течение жизненного цикла сущности. В этом руководстве мы обсудим события жизненного цикла объекта JPA и то, как мы можем использовать аннотации для обработки обратных вызовов и выполнения кода при возникновении этих событий.

Мы начнем с аннотирования методов самой сущности, а затем перейдем к использованию прослушивателя сущности.

2. События жизненного цикла сущности JPA

JPA определяет семь необязательных событий жизненного цикла, которые вызываются:

  • перед вызовом persist для новой сущности — @PrePersist
  • после вызова persist для нового объекта — @PostPersist
  • перед удалением объекта — @PreRemove
  • после удаления объекта — @PostRemove
  • перед операцией обновления – @PreUpdate
  • после обновления сущности — @PostUpdate
  • после загрузки объекта — @PostLoad

Существует два подхода к использованию аннотаций событий жизненного цикла: аннотирование методов в сущности и создание EntityListener с аннотированными методами обратного вызова. Мы также можем использовать оба одновременно. Независимо от того, где они находятся, методы обратного вызова должны иметь возвращаемый тип void .

Итак, если мы создаем новую сущность и вызываем метод сохранения нашего репозитория, вызывается наш метод, аннотированный @PrePersist , затем запись вставляется в базу данных и, наконец, вызывается наш метод @PostPersist . Если мы используем @GeneratedValue для автоматического создания наших первичных ключей, мы можем ожидать, что этот ключ будет доступен в методе @PostPersist .

Для операций @PostPersist , @PostRemove и @PostUpdate в документации упоминается, что эти события могут произойти сразу после выполнения операции, после сброса или в конце транзакции.

Мы должны отметить, что обратный вызов @PreUpdate вызывается только в том случае, если данные действительно изменены , то есть если есть фактический оператор обновления SQL для запуска. Обратный вызов @PostUpdate вызывается независимо от того, действительно ли что-то изменилось.

Если какой-либо из наших обратных вызовов для сохранения или удаления объекта выдает исключение, транзакция будет отменена.

3. Аннотирование объекта

Давайте начнем с использования аннотаций обратного вызова непосредственно в нашей сущности. В нашем примере мы собираемся оставить журнальный след при изменении записей пользователя , поэтому мы собираемся добавить простые операторы ведения журнала в наши методы обратного вызова.

Кроме того, мы хотим убедиться, что собираем полное имя пользователя после его загрузки из базы данных. Мы сделаем это, аннотировав метод с помощью @PostLoad .

Мы начнем с определения нашего объекта User :

@Entity
public class User {
private static Log log = LogFactory.getLog(User.class);

@Id
@GeneratedValue
private int id;

private String userName;
private String firstName;
private String lastName;
@Transient
private String fullName;

// Standard getters/setters
}

Далее нам нужно создать интерфейс UserRepository :

public interface UserRepository extends JpaRepository<User, Integer> {
public User findByUserName(String userName);
}

Теперь давайте вернемся к нашему классу User и добавим наши методы обратного вызова:

@PrePersist
public void logNewUserAttempt() {
log.info("Attempting to add new user with username: " + userName);
}

@PostPersist
public void logNewUserAdded() {
log.info("Added user '" + userName + "' with ID: " + id);
}

@PreRemove
public void logUserRemovalAttempt() {
log.info("Attempting to delete user: " + userName);
}

@PostRemove
public void logUserRemoval() {
log.info("Deleted user: " + userName);
}

@PreUpdate
public void logUserUpdateAttempt() {
log.info("Attempting to update user: " + userName);
}

@PostUpdate
public void logUserUpdate() {
log.info("Updated user: " + userName);
}

@PostLoad
public void logUserLoad() {
fullName = firstName + " " + lastName;
}

Когда мы запустим наши тесты, мы увидим серию операторов регистрации, исходящих из наших аннотированных методов. Кроме того, мы можем с уверенностью ожидать, что полное имя нашего пользователя будет заполнено при загрузке пользователя из базы данных.

4. Аннотирование EntityListener

Теперь мы собираемся расширить наш пример и использовать отдельный EntityListener для обработки наших обратных вызовов обновления. Мы могли бы предпочесть этот подход размещению методов в нашей сущности, если у нас есть какая-то операция, которую мы хотим применить ко всем нашим сущностям.

Давайте создадим наш AuditTrailListener для регистрации всей активности в таблице User :

public class AuditTrailListener {
private static Log log = LogFactory.getLog(AuditTrailListener.class);

@PrePersist
@PreUpdate
@PreRemove
private void beforeAnyUpdate(User user) {
if (user.getId() == 0) {
log.info("[USER AUDIT] About to add a user");
} else {
log.info("[USER AUDIT] About to update/delete user: " + user.getId());
}
}

@PostPersist
@PostUpdate
@PostRemove
private void afterAnyUpdate(User user) {
log.info("[USER AUDIT] add/update/delete complete for user: " + user.getId());
}

@PostLoad
private void afterLoad(User user) {
log.info("[USER AUDIT] user loaded from database: " + user.getId());
}
}

Как видно из примера, к методу можно применить несколько аннотаций .

Теперь нам нужно вернуться к нашей сущности User и добавить аннотацию @EntityListener к классу:

@EntityListeners(AuditTrailListener.class)
@Entity
public class User {
//...
}

И когда мы запустим наши тесты, мы получим два набора сообщений журнала для каждого действия обновления и сообщение журнала после загрузки пользователя из базы данных.

5. Вывод

В этой статье мы узнали, что такое обратные вызовы жизненного цикла объекта JPA и когда они вызываются. Мы посмотрели аннотации и поговорили о правилах их использования. Мы также экспериментировали с их использованием как в классе сущностей, так и в классе EntityListener .

Код примера доступен на GitHub .