Автор оригинала: 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 .