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.