实体事件
当使用 DataManager 读写数据时,Jmix 数据访问子系统会发送特定的 Spring 应用程序事件。可以创建事件监听器对保存和加载的实体实例做一些额外的操作。
使用 EntityChangedEvent
当实体保存至数据库时,框架会发送 EntityChangedEvent 事件。可以在保存事务内或者事务结束后处理该事件,无论哪种情况,此时数据库已经保存了变更后的数据。
EntityChangedEvent 包含变更操作的类型(创建、更新或删除)、变更实体的 id,还有变更属性的相关信息以及属性的旧值。对于引用属性,旧值包含引用实体的 id。
提交之前处理变更
如需在当前数据库事务中处理 EntityChangedEvent,创建一个带 @EventListener 注解的 bean 方法。框架会在实体保存至数据库但是事务尚未提交时调用该方法。在监听器方法中,可以对数据做任何修改,这些修改会与原始的修改一并提交。如果抛出任何异常,则回退所有操作。
下面的例子中,我们创建了 Customer 的一个关联实体,用来登记属性的改动。Customer 和 CustomerGradeChange 的变更会在同一个事务中提交:
@Component
public class CustomerEventListener {
    @Autowired
    private DataManager dataManager;
    @EventListener
    void onCustomerChangedBeforeCommit(EntityChangedEvent<Customer> event) {
        if (event.getType() != EntityChangedEvent.Type.DELETED  (1)
                && event.getChanges().isChanged("grade")) {     (2)
            registerGradeChange(
                    event.getEntityId(),                        (3)
                    event.getChanges().getOldValue("grade")     (4)
            );
        }
    }
    private void registerGradeChange(Id<Customer> customerId, CustomerGrade oldGrade) {
        Customer customer = dataManager.load(customerId).one(); (5)
        CustomerGradeChange gradeChange = dataManager.create(CustomerGradeChange.class);
        gradeChange.setCustomer(customer);
        gradeChange.setOldGrade(oldGrade);
        gradeChange.setNewGrade(customer.getGrade());
        dataManager.save(gradeChange);
    }
| 1 | 确定变更类型。 | 
| 2 | 确认属性修改。 | 
| 3 | 获取变更实体的 id。 | 
| 4 | 获取变更属性的旧值。 | 
| 5 | 加载变更实体的新状态。 | 
我们看看另一个例子。如果对 Order 实体的 OrderLine 进行新建、更新或删除,都会修改 Order 的 amount 属性:
@Component
public class OrderLineEventListener {
    @Autowired
    private DataManager dataManager;
    @EventListener
    void onOrderLineChangedBeforeCommit(EntityChangedEvent<OrderLine> event) {
        Order order;
        if (event.getType() == EntityChangedEvent.Type.DELETED) {               (1)
            Id<Order> orderId = event.getChanges().getOldReferenceId("order");  (2)
            order = dataManager.load(orderId).one();
        } else {
            OrderLine orderLine = dataManager.load(event.getEntityId()).one();
            order = orderLine.getOrder();
        }
        BigDecimal amount = order.getLines().stream()
                .map(line -> line.getProduct().getPrice().multiply(
                        BigDecimal.valueOf(line.getQuantity()))
                )
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        order.setAmount(amount);
        dataManager.save(order);
    }
}
| 1 | 当 OrderLine 实体被删除时,我们无法加载该实例,所以只能用旧值加载关联的 Order。 | 
| 2 | 用 getOldReference() 和 getOldCollection() 而非 getOldValue() 加载对一和对多的引用属性。 | 
提交之后处理变更
如需在变更提交至数据库完成之后处理 EntityChangedEvent,创建一个带 @TransactionalEventListener 注解的 bean 方法。
注意,此时在“提交之后”事件处理器中的异常不会返回给调用端,也不会记录。因此,推荐在 try-catch 中调用代码。
如需在“提交之后”事件处理器中加载或保存任何数据,都需要新起一个事务。
下面的例子中展示了异常处理和使用 DataManager 在单独的事务中加载实:
@Component
public class CustomerEventListener {
    private static final Logger log = LoggerFactory.getLogger(CustomerEventListener.class);
    @Autowired
    private DataManager dataManager;
    @TransactionalEventListener
    @Transactional(propagation = Propagation.REQUIRES_NEW) (1)
    void onCustomerChangedAfterCommit(EntityChangedEvent<Customer> event) {
        try {
            if (event.getType() != EntityChangedEvent.Type.DELETED
                    && event.getChanges().isChanged("grade")) {
                Customer customer = dataManager.load(event.getEntityId()).one();
                emailCustomerTheirNewGrade(customer.getEmail(), customer.getGrade());
            }
        } catch (Exception e) {
            log.error("Error handling Customer changes after commit", e);
        }
    }
| 1 | 使用 Propagation.REQUIRES_NEW 启动新事务。 | 
使用 EntitySavingEvent 和 EntityLoadingEvent
框架在将要保存至数据库时,会发送 EntitySavingEvent 事件。与包含实体 id 的 EntityChangedEvent 不同,EntitySavingEvent 包含实体本身。可以在保存至数据库之前修改实体的信息。
如果是在数据库表新增实例时发出的事件,则事件的 isNewEntity() 方法返回 true。
可以用 EntitySavingEvent 监听器在实体保存至数据库之前初始化实体属性。示例:
@Component
public class OrderEventListener {
    @EventListener
    void onOrderSaving(EntitySavingEvent<Order> event) {
        if (event.isNewEntity()) {
            Order order = event.getEntity();
            order.setNumber(generateOrderNumber());
        }
    }
当实体实例从数据库加载时,框架会发送 EntityLoadingEvent 事件。可以在该事件中,用实体的持久化属性初始化实体的非持久化属性。
| 
 在  对于实体引用,在   | 
下面的例子中,用 EntitySavingEvent 和 EntityLoadingEvent 维护一个加密属性:
@JmixEntity
@Table(name = "CUSTOMER")
@Entity(name = "sample_Customer")
public class Customer {
    @Column(name = "ENCRYPTED_DATA")
    @Lob
    private String encryptedData;
    @Transient
    @JmixProperty
    private String sensitiveData;
当实体保存实体时,敏感内容会被加密之后再存入数据库。而加载时,内容会先被解密然后设置到非持久化属性中以便用户访问:
@Component
public class CustomerEventListener {
    @Autowired
    private EncryptionService encryptionService;
    @EventListener
    void onCustomerSaving(EntitySavingEvent<Customer> event) {
        Customer customer = event.getEntity();
        String encrypted = encryptionService.encrypt(customer.getSensitiveData());
        customer.setEncryptedData(encrypted);
    }
    @EventListener
    void onCustomerLoading(EntityLoadingEvent<Customer> event) {
        Customer customer = event.getEntity();
        String sensitive = encryptionService.decrypt(customer.getEncryptedData());
        customer.setSensitiveData(sensitive);
    }