В статье о управлении сущностями в JPA я писал, что с точки зрения JPA у каждая управляемая EntityManager сущность имеет строго определённое состояние и строго определённые правила перехода из состояния в состояние (если углубляться в теорию, то речь идёт о конечном автомате). В JPA так же предусмотрен сравнительно несложный механизм обратных вызовов (callbacks) из EntityManager в те моменты, когда он меняет состояние сущностей. Это позволяет разработчику управлять процессом перехода сущности из состояния в состояние и реализовывать дополнительный функционал, например проверку уровней доступа, учёт обращений, ведение истории изменений и так далее. Идеологически механизм JPA callbacks похож на механизм событий в Hibernate.
Callback может быть определён как в классе сущности, так и в отдельном классе, связанным с классом сущности. Я использую модель данных из статьи о управлении сущностями в JPA и добавлю в неё обратные вызовы:
/** * Single financial operation. */ @Entity @EntityListeners(OperationListener.class) @Table(name = "journal", indexes = {@Index( name = "j_account_idx", columnList = "accountId", unique = false)}, uniqueConstraints = {@UniqueConstraint( columnNames = {"id", "accountId"})}) @SecondaryTable(name = "operations_details", pkJoinColumns = @PrimaryKeyJoinColumn( name = "op_id", referencedColumnName = "id")) public class Operation { /** * Operation's amount. */ @Getter @Setter @Column(nullable = false, updatable = false, scale = 2, precision = 10) private BigDecimal amount; /** * Optional operation description. */ @Getter @Setter @Column(table = "operations_details", length = 64) private String description; // Other fields @PostLoad public void postLoad() { System.out.println("Loaded operation: " + this.description + " with amount " + this.amount); } }
В классе сущности я определяю только один callback, вызываемый после загрузки сущности. Тип вызова задаётся аннотацией @PostLoad. Все остальные callbacks определены в классе OperationListener, с которым сущность Operation связана с помощью аннотации @EntityListeners.
/** * JPA callback example */ public class OperationListener { @PrePersist public void prePersist(Operation o) { System.out.println("Pre-Persistiting operation: " + o.getDescription()); } @PostPersist public void postPersist(Operation o) { System.out.println("Post-Persist operation: " + o.getDescription()); } @PreRemove public void preRemove(Operation o) { System.out.println("Pre-Removing operation: " + o.getDescription()); } @PostRemove public void postRemove(Operation o) { System.out.println("Post-Remove operation: " + o.getDescription()); } @PreUpdate public void preUpdate(Operation o) { System.out.println("Pre-Updating operation: " + o.getDescription()); } @PostUpdate public void postUpdate(Operation o) { System.out.println("Post-Update operation: " + o.getDescription()); } }
Всего, как следут из кода выше, есть 4 типа callbacks, три из которых вызываются до и после изменения.
- @PrePersist — вызывается как только инициирован вызов persist() и исполняется перед остальными действиями.
- @PostPersist — вызывается когда сохранение в базу завершено и оператор INSERT выполнен.
- @PreUpdate — вызывается перед сохранением изменений в сущности в базу.
- @PostUpdate — вызывается, когда данные сущности в базе обновлены и оператор UPDATE выполнен.
- @PreRemove — вызывается как только инициирован вызов remove() и исполняется перед остальными действиями.
- @PostRemove — вызывается, когда операция удаления из базы завершено и оператор DELETE выполнен.
- @PostLoad — вызывается после загрузки данных сущности из БД.
Стоит отметить, что помеченные @Pre функции вызываются всегда, когда вызывается метод, инициирующий изменение состояния сущности. В то время как @Post вызываются только тогда, когда операция уже была выполнена. И если операция не выполняется, то @Post метод не будет вызван. Например, если вы изменяете значение нескольких полей в сущности, а потом её удаляете вызовом remove(), то будет @PreUpdate метод будет вызван несколько раз, @PreRemove и @PostRemove будут вызываны по одному разу и @PostUpdate может быть не вызыван ни разу.
Теперь, когда все обработчики расставлены по своим местам осталось только проверить, как они работают. Для этого я создам сущность, изменю её значения, загружу из базы и удалю.
@Test public void testGreeter() { //New op Operation op = new Operation(); op.setId(1L); op.setAccountId(100500); op.setAmount(BigDecimal.TEN); op.setTimestamp(ZonedDateTime.now()); op.setDescription("Test operation"); op.setOpCode(9000); EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist(op); // op is MANAGED now em.flush(); op.setDescription("New operation name."); em.getTransaction().commit(); em.close(); // op is DETACHED now em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); op = em.find(Operation.class, 1L); em.remove(op); // op is REMOVED now em.getTransaction().commit(); em.close(); // op is DETACHED now }
Pre-Persistiting operation: Test operation Post-Persist operation: Test operation Pre-Updating operation: New operation name. Post-Update operation: New operation name. Loaded operation: New operation name. with amount 10.00 Pre-Removing operation: New operation name. Post-Remove operation: New operation name.