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"); } }
Если для сущности объявлены несколько слушателей одного типа (например, аннотациями класса сущности и его предков, плюс динамически), то их вызов будет выполняться в следующем порядке:
-
Для каждого предка, начиная с самого дальнего, вызываются его динамически добавленные слушатели, затем статически назначенные.
-
После всех предков вызываются динамически добавленные слушатели данного класса, затем статически назначенные.
-