JPA Callbacks

В статье о управлении сущностями в 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.

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *