3.4.7. EntityChangedEvent

EntityChangedEvent 是一个 Spring 的 ApplicationEvent 事件,会在实体保存到数据库时从中间层发送该事件。可以在事务中或者事务完成后处理该事件(使用 @TransactionalEventListener )。

只会为使用了 @PublishEntityChangedEvents 注解的实体发送该事件。如果实体需要监听 EntityChangedEvent 事件,别忘了为实体添加该注解。

EntityChangedEvent 不包含更改的实体本身,而只包含了其 id。还有,getOldValue(attributeName) 方法也只会返回引用的 id 而不是对象。所以如果需要的话,开发者可以使用合适的视图或者其它参数重新加载实体。

下面的例子展示了在当前事务中和事务后处理 Customer 实体的 EntityChangedEvent 事件。

  1. package com.company.demo.core;
  2. import com.company.demo.entity.Customer;
  3. import com.haulmont.cuba.core.app.events.AttributeChanges;
  4. import com.haulmont.cuba.core.app.events.EntityChangedEvent;
  5. import com.haulmont.cuba.core.entity.contracts.Id;
  6. import org.springframework.context.event.EventListener;
  7. import org.springframework.stereotype.Component;
  8. import org.springframework.transaction.event.TransactionPhase;
  9. import org.springframework.transaction.event.TransactionalEventListener;
  10. import java.util.UUID;
  11. @Component("demo_CustomerChangedListener")
  12. public class CustomerChangedListener {
  13. @TransactionalEventListener(
  14. phase = TransactionPhase.BEFORE_COMMIT (1)
  15. )
  16. public void beforeCommit(EntityChangedEvent<Customer, UUID> event) {
  17. Id<Customer, UUID> entityId = event.getEntityId(); (2)
  18. EntityChangedEvent.Type changeType = event.getType(); (3)
  19. AttributeChanges changes = event.getChanges();
  20. if (changes.isChanged("name")) { (4)
  21. String oldName = changes.getOldValue("name"); (5)
  22. // ...
  23. }
  24. }
  25. @TransactionalEventListener(
  26. phase = TransactionPhase.AFTER_COMMIT (6)
  27. )
  28. public void afterCommit(EntityChangedEvent<Customer, UUID> event) {
  29. (7)
  30. }
  31. }
1- 该监听器会在当前事务中调用。
2- 更改实体的 id。
3- 更改类型: CREATEDUPDATEDDELETED
4- 可以检查是否某个特定属性有变化。
5- 可以获取变化属性的旧值。
6- 该监听器在事务提交之后会被调用。
7- 在事务提交之后,事件包含跟提交之前相同的信息。

如果监听器在事务内部调用,可以通过抛出异常的方法回滚事务,这样不会有数据保存至数据库。如果不想用户看到任何错误提示,可以用 SilentException

如果一个 “after commit” 监听器抛出了异常,该异常会被日志记录,而不会呈现给客户端(用户不会在 UI 看到该错误)。

如果在当前事务(TransactionPhase.BEFORE_COMMIT)中处理 EntityChangedEvent,请确保使用了 TransactionalDataManager 从数据库获取更改实体的当前状态。如果使用的是 DataManager,它会创建新的数据库事务,如果尝试读取未提交的数据会容易导致数据库死锁。

在 “after commit” 监听器(TransactionPhase.AFTER_COMMIT)中,在使用 TransactionalDataManager 之前需要使用 DataManager 或者显式创建一个新事务。

下面是使用 EntityChangedEvent 更新关联实体的示例。

假设根据 快速开始-Sales应用程序 ,我们有 OrderOrderLineProduct 实体。但是 Product 还有额外的 special 布尔类型属性,并且 OrdernumberOfSpecialProducts 整型属性,该属性需要根据每次从 Order 中添加或者删除 OrderLine 时重新计算。

创建下面带有 @EventListener 方法的类,此方法会在事务提交之前, OrderLine 实体发生改变时调用。

  1. package com.company.sales.listener;
  2. import com.company.sales.entity.Order;
  3. import com.company.sales.entity.OrderLine;
  4. import com.haulmont.cuba.core.TransactionalDataManager;
  5. import com.haulmont.cuba.core.app.events.EntityChangedEvent;
  6. import com.haulmont.cuba.core.entity.contracts.Id;
  7. import org.springframework.context.event.EventListener;
  8. import org.springframework.stereotype.Component;
  9. import javax.inject.Inject;
  10. import java.util.UUID;
  11. @Component("sales_OrderLineChangedListener")
  12. public class OrderLineChangedListener {
  13. @Inject
  14. private TransactionalDataManager txDm;
  15. @TransactionalEventListener(
  16. phase = TransactionPhase.BEFORE_COMMIT
  17. )
  18. public void beforeCommit(EntityChangedEvent<OrderLine, UUID> event) {
  19. Order order;
  20. if (event.getType() != EntityChangedEvent.Type.DELETED) { (1)
  21. order = txDm.load(event.getEntityId()) (2)
  22. .view("orderLine-with-order") (3)
  23. .one()
  24. .getOrder(); (4)
  25. } else {
  26. Id<Order, UUID> orderId = event.getChanges().getOldReferenceId("order"); (5)
  27. order = txDm.load(orderId).one();
  28. }
  29. long count = txDm.load(OrderLine.class) (6)
  30. .query("select o from sales_OrderLine o where o.order = :order")
  31. .parameter("order", order)
  32. .view("orderLine-with-product")
  33. .list().stream()
  34. .filter(orderLine -> Boolean.TRUE.equals(orderLine.getProduct().getSpecial()))
  35. .count();
  36. order.setNumberOfSpecialProducts((int) count);
  37. txDm.save(order); (7)
  38. }
  39. }
1- 如果没有删除 OrderLine,我们可以使用 id 从数据库加载。
2- event.getEntityId() 方法返回更改的 OrderLine id。
3- 使用包含 OrderLine 并带有其关联 Order 的视图。试图必须包含 Order.numberOfSpecialProducts 属性,因为我们之后会更新这个值。
4- 从加载的 OrderLine 中获取 Order
5- 如果 OrderLine 已经被删除了,则不能从数据库加载,但是 event.getChanges() 方法会返回实体的所有属性,也包含了关联实体的 id。所以我们可以用 id 从中获取关联的 Order
6- 为给定的 Order 加载所有的 OrderLine 实例,使用 Product.special 进行过滤并对它们进行计数。视图必须包含 OrderLine 以及关联的 Product
7- 改变了属性之后再保存 Order