В статье о управлении сущностями в JPA я писал, что с точки зрения JPA у каждая управляемая EntityManager сущность имеет строго определённое состояние и строго определённые правила перехода из состояния в состояние (если углубляться в теорию, то речь идёт о конечном автомате). В JPA так же предусмотрен сравнительно несложный механизм обратных вызовов (callbacks) из EntityManager в те моменты, когда он меняет состояние сущностей. Это позволяет разработчику управлять процессом перехода сущности из состояния в состояние и реализовывать дополнительный функционал, например проверку уровней доступа, учёт обращений, ведение истории изменений и так далее. Идеологически механизм JPA callbacks похож на механизм событий в Hibernate.
Callback может быть определён как в классе сущности, так и в отдельном классе, связанным с классом сущности. Я использую модель данных из статьи о управлении сущностями в JPA и добавлю в неё обратные вызовы:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/**
* 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/**
* 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 может быть не вызыван ни разу.
Теперь, когда все обработчики расставлены по своим местам осталось только проверить, как они работают. Для этого я создам сущность, изменю её значения, загружу из базы и удалю.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@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
}
|
1
2
3
4
5
6
7
|
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.
|
Код примера доступен на github.