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 .