Рубрики
Без рубрики

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

Изучите, что такое обратные вызовы жизненного цикла сущности JPA и когда они вызываются.

Автор оригинала: Amy DeGregorio.

1. введение

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

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

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

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

  • прежде чем persist будет вызван для новой сущности – @prePersist
  • после того, как persist вызывается для новой сущности – @postPersist
  • перед удалением объекта – @PreRemove
  • после удаления объекта – @PostRemove
  • перед операцией обновления – @preUpdate
  • после обновления сущности – @PostUpdate
  • после загрузки объекта – @postLoad

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

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

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

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

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

3. Аннотирование сущности

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

Кроме того, мы хотим убедиться, что мы собираем полное имя пользователя после его загрузки из базы данных. Мы сделаем это, аннотировав метод с помощью @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 {
    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. Аннотирование прослушивателя сущностей

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

Давайте создадим наш прослушиватель Аудиторского следа для регистрации всех действий в таблице 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 и добавить аннотацию @Entitylisteners в класс:

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

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

5. Заключение

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

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