This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Entity Listener
Entity listener are used to make changes to entities or run logic when they get created, updated or deleted before the database transaction is committed (except CollectingAfterCommitListener). Listeners listen to events (create, update, delete) of defined entities. A listener is registered on at least one entity model. It is possible to register the same listener on multiple entity models.
Listeners are spring beans which should be annotated with ch.tocco.nice2.persist.core.api.entity.events.Listener.
The filter property of the annotation can be used to restrict to which entities the listener should be applied.
Examples:
Note
Restrict to a single entity:
@Listener(filter = "User")To apply the listener to multiple entities an array should be used:
@Listener(filter = {"User", "Address"})
See Listeners for a more technical documentation of the listener.
Warning
Do not use Entity Listeners if the goal also can be reached by Default-Values
Dependencies
Support for entity listeners is added by the core:persist:core module:
dependencies {
implementation project(":core:persist:core")
}
It is however very likely that this module is already transitively included by another module.
CollectingEntityListener
Collecting entity listeners fetch all EntityFacadeEvent until the current transaction is about to commit. This means a collecting entity listener is executed only once right before the commit. All EntityFacadeEvents which are produced later, for example by an other collecting entity listener which runs after the current, are discarded.
To add a CollectingEntityListener add a Java class which
extends the class CollectingEntityListener.
The method onBeforeCommit must be overwritten. This is the method
which gets called by the nice2 framework just before the commit takes place.
@Listener(filter = "...")
@ThreadScope
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
// your code goes here
}
}
Important
The thread scope must be used for collecting entity listeners (@ThreadScope annotation). This means that the service
is instantiated once per Thread and bound to this Thread
only. This is needed because the collecting entity listener fetches every EntityFacadeEvent
of one transaction only.
As long as the @Listener annotation is used, the listener will be registered automatically.
Handle Events
The class CollectingEntityListener provides some useful
methods to handle the EntityFacadeEvents correctly.
The most important methods are described here. Open the class ch.tocco.nice2.persist.util.CollectingEntityListener to
see all methods.
Do something with all created entities
The method withCreatedEntities calls the given Consumer<Entity> with all created entities with the entity
model which the listener was contributed to listen to.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
withCreatedEntities(entity -> {
// Your code goes here
});
}
}
Hint
There is also the older method getCreatedEntities which returns a list of the entities. However, this method
should only be used if you can’t work with withCreatedEntities for some reason. getCreatedEntities is the
less preferred method usually as it’s slower and needs more memory, because it has to create the list to return.
Do something with all updated entities
The method withUpdatedEntities calls the given Consumer<Entity> with all updated entities with the entity
model which the listener was contributed to listen to.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
withUpdatedEntities(entity -> {
// Your code goes here
});
}
}
Hint
There is also the older method getUpdatedEntities which returns a list of the entities. However, this method
should only be used if you can’t work with withUpdatedEntities for some reason. getUpdatedEntities is the
less preferred method usually as it’s slower and needs more memory, because it has to create the list to return.
Do something with all deleted entities
The method withDeletedEntities calls the given Consumer<Entity> with all deleted entities with the entity
model which the listener was contributed to listen to.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
withDeletedEntities(entity -> {
// Your code goes here
});
}
}
Hint
There is also the older method getDeletedEntities which returns a list of the entities. However, this method
should only be used if you can’t work with withDeletedEntities for some reason. getDeletedEntities is the
less preferred method usually as it’s slower and needs more memory, because it has to create the list to return.
Do something with affected entities
The method withAffectedEntities calls the given Consumer<Entity> with all created and updated entities
with the entity model which the listener was contributed to listen to.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
withAffectedEntities(entity -> {
// Your code goes here
});
}
}
Hint
There is also the older method getAffectedEntities which returns a list of the entities. However, this method
should only be used if you can’t work with withAffectedEntities for some reason. getAffectedEntities is the
less preferred method usually as it’s slower and needs more memory, because it has to create the list to return.
Do something with all affected entities
The method withAllAffectedEntities calls the given Consumer<Entity> with all created, updated
and deleted entities with the entity model which the listener was contributed to listen to.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
withAllAffectedEntities(entity -> {
// Your code goes here
});
}
}
Hint
There is also the older method getAllAffectedEntities which returns a list of the entities. However, this method
should only be used if you can’t work with withAllAffectedEntities for some reason. getAllAffectedEntities is
the less preferred method usually as it’s slower and needs more memory, because it has to create the list to return.
Using the Context in Collecting Entity Listeners
The context can be received by the Transaction passed to the
onBeforeCommit method.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
Context context = tx.getContext();
// Your code goes here
}
}
Important
Do not inject the Context in a CollectingEntityListener but get it from the passed Transaction
The Order of Collecting Entity Listeners
Collecting entity listeners are not ordered by default.
However if ordering is necessary for a number of listeners the priority() method
can be overridden.
EntityFacadeListener
Note
The InterruptibleEntityFacadeAdapter should not be used any longer as the persistence framework
does not throw any InterruptedExceptions any more.
An EntityFacadeListener is
executed for every EntityFacadeEvent which belongs to
the entity on which the listener is contributed to listen to. This means every time setValue or a similar method
is called on the entity the EntityFacadeListener
is executed.
To add a EntityFacadeListener
add a Java class which implements the class and annotate it with @Listener.
@Listener(filter = "...")
public class MyEntityFacadeAdapter implements EntityFacadeListener { ... }
Now depending on what the listener needs to do there are several methods which can be overridden.
entityCreating
This method gets called if a new entity was created.
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityCreating(EntityFacadeEvent event) {
Entity user = event.getSource();
// do something with `user`
}
}
entityDeleting
This method gets called if an entity was deleted.
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityDeleting(EntityFacadeEvent event) {
Entity user = event.getSource();
// do something with `user`
}
}
entityChanging
This method gets called if any changes are made to an entity.
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityChanging(EntityChangedEvent event) {
if ("field_name".equals(event.getField().getName())) {
// do something with `user`
}
}
}
EntityChangedEvent
It is important to only process the listener if it is really necessary. Lets say a listener must set a flag isAdult
on Users if they’re older than 18 years. This could be done like this:
// Bad example
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityChanging(EntityChangedEvent event) {
Entity user = event.getSource();
if (isAdult(user) {
user.setValue("is_adult", true);
}
}
}
This would work without any problems. But most probably this listener would be executed a lot of times even it would not
be necessary. Because EntityFacadeListener
are executed every time setValue is called on the entity, this listener is also executed if for example only the name
of the user was changed. The name has nothing to do with the age of user.
A EntityChangedEvent is passed to the method
entityChanging which has some additional methods to work with. The above example
can be rewritten to the following:
// Good example
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityChanging(EntityChangedEvent event) {
if ("birthdate".equals(event.getField().getName())) {
LocaleDate birthdate = (LocalDate) event.getNewValue();
if (isAdult(birthdate) {
user.setValue("is_adult", true);
}
}
}
}
Before the whole logic (setting the adult flag) is processed, we check if the change which is done to the entity belongs to
the field birthdate because this is the only field which is relevant for this listener. Then instead of reading the
field birthdate from the entity we just call the method getNewValue on the
A EntityChangedEvent. Because getNewValue returns
an Object it needs to be cast first.
entityRelationChanging
This method gets called if a relation on the entity was changed.
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityRelationChanging(EntityRelationChangedEvent event) {
if("relRelation_name".equals(event.getRelation().getName())) {
// Your code goes here
}
}
}
EntityRelationChangedEvent
A EntityRelationChangedEvent is passed to
the method entityRelationChanging which has some additional methods to work with.
To check what relation was changed the method getRelation can get called on the
EntityRelationChangedEvent.
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityRelationChanging(EntityRelationChangedEvent event) {
Relation relation = event.getRelation();
String relationName = relation.getName(); // e.g. `relUser`
}
}
There also methods to check how the relation got changed.
public class MyEntityFacadeAdapter implements EntityFacadeListener {
@Override
public void entityRelationChanging(EntityRelationChangedEvent event) {
if (event.isAdded()) {
// code is executed when the relation was added to the entity.
// E.g. User.relUser_status was set to `active`
}
if (event.isRemoved()) {
// code is executed when the relation was removed.
// E.g. User.relUser_status was cleared
}
if (event.isAdjusting()) {
// code is executed if the relation was changed.
// E.g. User.relUser_status was changed from `active` to `archived`
}
}
}
Avoid Infinite Loops
With EntityFacadeListeners it is possible to create infinite loops. Because these listeners are executed every time a change has made to the entity which the listener listens to. In the picture below an example of an infinite loop is shown (example does not make any sense).
Both listeners listen to changes on the entity User. Listener A listens on changes on the field firstname and
sets a value on the field lastname. Listener B listens to the field lastname which was set from the listener
A and sets a value on the field firstname. Now listener A again is executed and so on.
CollectingAfterCommitListener
CollectingAfterCommitListeners are fired after the transaction was committed. This can be useful if something only must be done if something else was persisted before. For example mails are sent often with CollectingAfterCommitListeners. Lets say a user should receive an e-mail if he was registered to an event. This could be done within a EntityFacadeListener or CollectingEntityListener.
public class MyCollectingEntityListener extends CollectingEntityListener {
@Override
public void onBeforeCommit(Transaction tx) {
getCreatedEntities().forEach(registration -> {
sendMailTo(registration); // what if later in an other entity listener something goes wrong?
});
}
}
But if the current transaction for some reason fails, it will be rolled back and the registration entity is not persisted. In this case the user would have received an e-mail but was not actually registered to the event.
That is when CollectingAfterCommitListeners are useful.
CollectingAfterCommitListeners need to be annotated
with the @Listener annotation the same way as the other listeners.
The listener must extend the class
CollectingAfterCommitListener and overwrite the
method getAfterCommitTask which returns an AfterCommitTask.
@Listener(filter = "...")
@ThreadScope
public class MyCollectingAfterCommitListener extends CollectingAfterCommitListener {
public MyEntityFacadeAdapter(CommandExecutor commandExecutor) {
super(commandExecutor);
}
@Override
protected AfterCommitTask getAfterCommitTask() {
return new AfterCommitTask() {
@Override
public void onAfterCommit(CommandContext commandContext) throws Exception {
// Your code goes here
}
};
}
}
Note
A CollectingAfterCommitListener does not know
what has changed on the entities itself. But if the entity got created, updated or deleted is known. Like the normal
collecting entity listener it is necessary to use the @ThreadScope annotation.