3.4.4.6. Entity Listeners

Entity Listeners предназначены для реакции на события жизненного цикла экземпляров сущностей на уровне Middleware.

Слушатель представляет собой класс, реализующий один или несколько интерфейсов пакета com.haulmont.cuba.core.listener. Слушатель будет реагировать на события типов, соответствующих реализуемым интерфейсам.

BeforeDetachEntityListener

Метод onBeforeDetach() вызывается перед отделением объекта от EntityManager при коммите транзакции.

Данный слушатель можно использовать, например, для заполнения неперсистентных атрибутов сущности перед отправкой ее на клиентский уровень.

BeforeAttachEntityListener

Метод onBeforeAttach() вызывается перед введением объекта в персистентный контекст при выполнении операции EntityManager.merge().

Данный слушатель можно использовать, например, для заполнения персистентных атрибутов сущности перед сохранением ее в базе данных.

BeforeInsertEntityListener

Метод onBeforeInsert() вызывается перед выполнением вставки записи в БД. В данном методе возможны любые операции с текущим EntityManager.

AfterInsertEntityListener

Метод onAfterInsert() вызывается после выполнения вставки записи в БД, но до коммита транзакции. В данном методе нельзя модифицировать текущий персистентный контекст, однако можно производить изменения в БД с помощью QueryRunner.

BeforeUpdateEntityListener

Метод onBeforeUpdate() вызывается перед изменением записи в БД. В данном методе возможны любые операции с текущим EntityManager.

AfterUpdateEntityListener

Метод onAfterUpdate() вызывается после изменения записи в БД, но до коммита транзакции. В данном методе нельзя модифицировать текущий персистентный контекст, однако можно производить изменения в БД с помощью QueryRunner.

BeforeDeleteEntityListener

Метод onBeforeDelete() вызывается перед удалением записи из БД (или в случае мягкого удаления - перед изменением записи). В данном методе возможны любые операции с текущим EntityManager.

AfterDeleteEntityListener

Метод onAfterDelete() вызывается после удаления записи из БД (или в случае мягкого удаления - после изменения записи), но до коммита транзакции. В данном методе нельзя модифицировать текущий персистентный контекст, однако можно производить изменения в БД с помощью QueryRunner.

Entity Listener должен являться Spring-бином, поэтому в нем можно использовать инжекцию в поля и сеттеры. Для всех экземпляров некоторого класса сущности создается один экземпляр слушателя определенного типа, поэтому слушатель не должен иметь изменяемого состояния.

Следует иметь в виду, что для BeforeInsertEntityListener фреймворк гарантирует managed state только для корневой сущности, принимаемой слушателем. Ее ссылки, встречающиеся в графе объектов, могут быть в состоянии detached. Поэтому если вам необходимо изменять эти объекты, используйте метод EntityManager.merge(), а если только иметь возможность обращаться к любым их атрибутам, то метод EntityManager.find(). Например:

package com.company.sample.listener;

import com.company.sample.core.DiscountCalculator;
import com.company.sample.entity.*;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.listener.*;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import java.math.BigDecimal;

@Component("sample_OrderEntityListener")
public class OrderEntityListener implements
        BeforeInsertEntityListener<Order>,
        BeforeUpdateEntityListener<Order>,
        BeforeDeleteEntityListener<Order> {

    @Inject
    private DiscountCalculator discountCalculator; // a managed bean of the middle tier

    @Override
    public void onBeforeInsert(Order entity, EntityManager entityManager) {
        calculateDiscount(entity.getCustomer(), entityManager);
    }

    @Override
    public void onBeforeUpdate(Order entity, EntityManager entityManager) {
        calculateDiscount(entity.getCustomer(), entityManager);
    }

    @Override
    public void onBeforeDelete(Order entity, EntityManager entityManager) {
        calculateDiscount(entity.getCustomer(), entityManager);
    }

    private void calculateDiscount(Customer customer, EntityManager entityManager) {
        if (customer == null)
            return;

        // Delegate calculation to a managed bean of the middle tier
        BigDecimal discount = discountCalculator.calculateDiscount(customer.getId());

        // Merge customer instance because it comes to onBeforeInsert as part of another
        // entity's object graph and can be detached
        Customer managedCustomer = entityManager.merge(customer);

        // Set the discount for the customer. It will be saved on transaction commit.
        managedCustomer.setDiscount(discount);
    }
}

Все слушатели кроме BeforeAttachEntityListener выполняются внутри транзакции. Это значит, что если внутри слушателя выбрасывается исключение, текущая транзакция откатывается и все изменения в базе данных теряются.

Если вам необходимо выполнить некоторые действия после успешного завершения транзакции, используйте callback-интерфейс TransactionSynchronization фреймворка Spring для того чтобы отложить выполнение до нужной фазы транзакции. Например:

package com.company.sales.service;

import com.company.sales.entity.Customer;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.listener.BeforeInsertEntityListener;
import com.haulmont.cuba.core.listener.BeforeUpdateEntityListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Component("sales_CustomerEntityListener")
public class CustomerEntityListener implements BeforeInsertEntityListener<Customer>, BeforeUpdateEntityListener<Customer> {

    @Override
    public void onBeforeInsert(Customer entity, EntityManager entityManager) {
        printCustomer(entity);
    }

    @Override
    public void onBeforeUpdate(Customer entity, EntityManager entityManager) {
        printCustomer(entity);
    }

    private void printCustomer(Customer customer) {
        System.out.println("In transaction: " + customer);

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                System.out.println("After transaction commit: " + customer);
            }
        });
    }
}
Регистрация entity listeners

Entity Listener может быть задан двумя способами:

  • Статически - имена бинов слушателей указываются в аннотации @Listeners на классе сущности:

    @Entity(...)
    @Table(...)
    @Listeners("sample_MyEntityListener")
    public class MyEntity extends StandardEntity {
        ...
    }
  • Динамически - имя бина слушателя передается в метод addListener() бина EntityListenerManager. Таким способом можно добавить слушатель для сущности, находящейся в компоненте приложения. В примере ниже слушатель, реализованный бином sample_UserEntityListener, добавляется сущности User, которая определена во фреймворке:

    package com.company.sample.core;
    
    import com.haulmont.cuba.core.global.Events;
    import com.haulmont.cuba.core.sys.events.AppContextInitializedEvent;
    import com.haulmont.cuba.core.sys.listener.EntityListenerManager;
    import com.haulmont.cuba.security.entity.User;
    import org.springframework.context.event.EventListener;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import javax.inject.Inject;
    
    @Component("sample_AppLifecycle")
    public class AppLifecycle {
    
        @Inject
        private EntityListenerManager entityListenerManager;
    
        @EventListener(AppContextInitializedEvent.class) // notify after AppContext is initialized
        @Order(Events.LOWEST_PLATFORM_PRECEDENCE + 100)  // run after all framework listeners
        public void initEntityListeners() {
            entityListenerManager.addListener(User.class, "sample_UserEntityListener");
        }
    }

Если для сущности объявлены несколько слушателей одного типа (например, аннотациями класса сущности и его предков, плюс динамически), то их вызов будет выполняться в следующем порядке:

  1. Для каждого предка, начиная с самого дальнего, вызываются его динамически добавленные слушатели, затем статически назначенные.

  2. После всех предков вызываются динамически добавленные слушатели данного класса, затем статически назначенные.