«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.