3.4.7. EntityChangedEvent
See Decouple Business Logic with Application Events guide to learn how to use |
EntityChangedEvent
is a Spring’s ApplicationEvent
which is sent by the framework on the middle tier when an entity instance is saved to the database. The event can be handled both inside the transaction (using @EventListener
) and after its completion (using @TransactionalEventListener).
The event is sent only for entities annotated with |
EntityChangedEvent
does not contain the changed entity instance but only its id. Also, the getOldValue(attributeName)
method returns ids of references instead of objects. So if needed, the developer should reload entities with a required view and other parameters.
Below is an example of handling the EntityChangedEvent
for a Customer
entity in the current transaction and after its completion:
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 | - this listener is invoked inside the current transaction. |
2 | - changed entity’s id. |
3 | - change type: CREATED , UPDATED or DELETED . |
4 | - you can check if a particular attribute has been changed. |
5 | - you can get the old value of a changed attribute. |
6 | - this listener is invoked after the transaction is committed. |
7 | - after transaction commit, the event contains the same information as before commit. |
If the listener is invoked inside the transaction, you can roll it back by throwing an exception. Nothing will be saved in the database then. If you don’t want any notifications to the user, use SilentException
.
If an "after commit" listener throws an exception, it will be logged, but not propagated to the client (the user won’t see the error in UI).
When handling In "after commit" listeners ( |
Below is an example of using EntityChangedEvent
to update related entities.
Suppose we have Order
, OrderLine
and Product
entities as in the Sales Application, but Product
additionally has special
boolean attribute and Order
has numberOfSpecialProducts
integer attribute which should be recalculated each time an OrderLine
is created or deleted from the Order
.
Create the following class with the @EventListener
method which will be invoked for changed OrderLine
entities before transaction commit:
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 | - if OrderLine is not deleted, we can load it from the database by id. |
2 | - event.getEntityId() method returns id of the changed OrderLine . |
3 | - use a view that contains OrderLine together with the Order it belongs to. The view must contain the Order.numberOfSpecialProducts attribute because we need to update it later. |
4 | - get Order from the loaded OrderLine . |
5 | - if OrderLine has just been deleted, it cannot be loaded from the database, but event.getChanges() method returns all attributes of the entity, including identifiers of related entities. So we can load related Order by its id. |
6 | - load all OrderLine instances for the given Order , filter them by Product.special and count them. The view must contain OrderLine together with the related Product . |
7 | - save Order after changing its attribute. |