3.4.4.6. Entity Listeners
Entity Listeners are designed to react to the entity instances lifecycle events on the middle tier.
A listener is a class implementing one or several interfaces from the com.haulmont.cuba.core.listener
package. The listener will react to events corresponding to the implemented interfaces.
- BeforeDetachEntityListener
-
onBeforeDetach()
method is called before the object is detached from EntityManager on transaction commit.This listener can be used for populating non-persistent entity attributes before sending it to the client tier.
- BeforeAttachEntityListener
-
onBeforeAttach()
method is called before the object is attached to the persistence context as a result ofEntityManager.merge()
operation.This listener can be used, for example, to populate persistent entity attributes before saving it to the database.
- BeforeInsertEntityListener
-
onBeforeInsert()
method is called before a record is inserted into the database. All kinds of operations can be performed with the currentEntityManager
available within this method. - AfterInsertEntityListener
-
onAfterInsert()
is called after a record is inserted into database, but before transaction commit. This method does not allow modifications of the current persistence context, however, database modifications can be done using QueryRunner. - BeforeUpdateEntityListener
-
onBeforeUpdate()
method is called before a record is updated in the database. All kinds of operations can be performed with the currentEntityManager
available within this method. - AfterUpdateEntityListener
-
onAfterUpdate()
method is called after a record was updated in the database, but before transaction commit. This method does not allow modifications of the current persistence context, however, database modifications can be done usingQueryRunner
. - BeforeDeleteEntityListener
-
onBeforeDelete()
method is called before a record is deleted from the database (in the case of soft deletion – before updating a record). All kinds of operations can be performed with the currentEntityManager
available within this method. - AfterDeleteEntityListener
-
onAfterDelete()
method is called after a record is deleted from the database (in the case of soft deletion – after updating a record), but before transaction commit. This method does not allow modifications of the current persistence context, however, database modifications can be done usingQueryRunner
.
An entity listener must be a Spring bean, so you can use injection in its fields and setters. Only one listener instance of a certain type is created for all instances of a particular entity class, therefore listener must not have a mutable state.
Be aware that for BeforeInsertEntityListener
the framework guarantees managed state only for the root entity coming to the listener. Its references down to the object graph can be in the detached state. So you should use EntityManager.merge()
method if you need to update these objects or EntityManager.find()
to be able to access all their attributes. For example:
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);
}
}
All listeners except BeforeAttachEntityListener
work within a transaction. It means that if an exception is thrown inside the listener, the current transaction is rolled back and all database changes are discarded.
If you need to perform some actions after successful transaction commit, use Spring’s TransactionSynchronization
callback to defer execution to a desired transaction phase. For example:
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);
}
});
}
}
- Registration of entity listeners
-
An entity listener can be specified for an entity in two ways:
-
Statically – the bean names of listeners are listed in @Listeners annotation on the entity class.
@Entity(...) @Table(...) @Listeners("sample_MyEntityListener") public class MyEntity extends StandardEntity { ... }
-
Dynamically – the bean name of the listener is passed to the
addListener()
method of theEntityListenerManager
bean. This way you can add a listener to an entity located in an application component. In the example below, we add the entity listener implemented by thesample_UserEntityListener
bean to theUser
entity defined in the framework: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"); } }
If several listeners of the same type (for example from annotations of entity class and its parents and also added dynamically) were declared for an entity, they will be invoked in the following order:
-
For each ancestor, starting from the most distant one, dynamically added listeners are invoked first, followed by statically assigned listeners.
-
Once parent classes are processed, dynamically added listeners for the given class are invoked first, followed by statically assigned.
-