3.4.7. EntityChangedEvent
Руководство Decouple Business Logic with Application Events демонстрирует использование |
EntityChangedEvent
- это ApplicationEvent
, который посылается фреймворком на среднем слое, когда некоторый экземпляр сущности сохраняется в базу данных. Данное событие может быть обработано как внутри текущей транзакции (используя @EventListener
), так и после ее завершения (при использовании @TransactionalEventListener).
Событие посылается только если на сущности есть аннотация |
Объект EntityChangedEvent
содержит не сам измененный экземпляр сущности, а только его id. Кроме того, метод getOldValue(attributeName)
возвращает идентификаторы ссылок вместо самих объектов. Поэтому при необходимости, разработчик должен загрузить требуемые сущности с указанием требуемого представления и других параметров.
Ниже приведен пример обработки EntityChangedEvent
для сущности Customer
в текущей транзакции и после ее завершения:
package com.company.demo.core;
import com.company.demo.entity.Customer;
import com.haulmont.cuba.core.app.events.AttributeChanges;
import com.haulmont.cuba.core.app.events.EntityChangedEvent;
import com.haulmont.cuba.core.entity.contracts.Id;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;
import java.util.UUID;
@Component("demo_CustomerChangedListener")
public class CustomerChangedListener {
@EventListener (1)
public void beforeCommit(EntityChangedEvent<Customer, UUID> event) {
Id<Customer, UUID> entityId = event.getEntityId(); (2)
EntityChangedEvent.Type changeType = event.getType(); (3)
AttributeChanges changes = event.getChanges();
if (changes.isChanged("name")) { (4)
String oldName = changes.getOldValue("name"); (5)
// ...
}
}
@TransactionalEventListener (6)
public void afterCommit(EntityChangedEvent<Customer, UUID> event) {
(7)
}
}
1 | - данный обработчик вызывается внутри текущей транзакции. |
2 | - id измененной сущности. |
3 | - тип изменения: CREATED , UPDATED or DELETED . |
4 | - можно проверить, изменился ли определенный атрибут. |
5 | - можно получить старое значение измененного атрибута. |
6 | - данный обработчик вызывается после коммита транзакции. |
7 | - после коммита событие содержит те же значения что и внутри транзакции. |
Если обработчик вызывается внутри транзакции, ее можно откатить путем выбрасывания исключения. При этом в БД никакие изменения не сохранятся. Если вы не хотите, чтобы пользователь получил какое-либо оповещение, используйте SilentException
.
Если "after commit" обработчик выбрасывает исключение, оно будет залоггировано, но не передано клиенту (т.е. пользователь не получит сообщения об ошибке в UI).
При обработке В обработчике, вызываемом после коммита транзакции ( |
Ниже приведен пример использования EntityChangedEvent
для изменения связанных сущностей.
Предположим, имеются сущности Order
, OrderLine
и Product
, как в приложении Sales, но сущность Product
дополнительно имеет булевский атрибут special
, а у сущности Order
есть атрибут numberOfSpecialProducts
, который должен быть пересчитан при создании и удалении экземпляров OrderLine
в составе Order
.
Создадим следующий класс с методом, аннотированным @EventListener
, который будет вызываться при изменении сущностей OrderLine
перед коммитом транзакции:
package com.company.sales.listener;
import com.company.sales.entity.Order;
import com.company.sales.entity.OrderLine;
import com.haulmont.cuba.core.TransactionalDataManager;
import com.haulmont.cuba.core.app.events.EntityChangedEvent;
import com.haulmont.cuba.core.entity.contracts.Id;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import java.util.UUID;
@Component("sales_OrderLineChangedListener")
public class OrderLineChangedListener {
@Inject
private TransactionalDataManager txDm;
@EventListener
public void beforeCommit(EntityChangedEvent<OrderLine, UUID> event) {
Order order;
if (event.getType() != EntityChangedEvent.Type.DELETED) { (1)
order = txDm.load(event.getEntityId()) (2)
.view("orderLine-with-order") (3)
.one()
.getOrder(); (4)
} else {
Id<Order, UUID> orderId = event.getChanges().getOldReferenceId("order"); (5)
order = txDm.load(orderId).one();
}
long count = txDm.load(OrderLine.class) (6)
.query("select o from sales_OrderLine o where o.order = :order")
.parameter("order", order)
.view("orderLine-with-product")
.list().stream()
.filter(orderLine -> Boolean.TRUE.equals(orderLine.getProduct().getSpecial()))
.count();
order.setNumberOfSpecialProducts((int) count);
txDm.save(order); (7)
}
}
1 | - если экземпляр OrderLine не удален, можно загрузить его из БД по идентификатору. |
2 | - метод event.getEntityId() возвращает id измененного экземпляра OrderLine . |
3 | - используем представление, которое содержит OrderLine вместе с Order , которому он принадлежит. Представление должно содержать атрибут Order.numberOfSpecialProducts , так как его необходимо будет обновить. |
4 | - получаем Order из загруженного OrderLine . |
5 | - если экземпляр OrderLine был только что удален, его нельзя загрузить из БД, но метод event.getChanges() возвращает все атрибуты удаленной сущности, включая идентификаторы связанных сущностей. Поэтому можно загрузить связанный Order по его id. |
6 | - загружаем все экземпляры OrderLine для данного Order , отфильтровываем по Product.special и считаем их. Представление должно содержать OrderLine вместе со связанным Product . |
7 | - сохраняем Order после изменения его атрибута. |