164 lines
6.1 KiB
Plaintext
164 lines
6.1 KiB
Plaintext
[[entity-callbacks]]
|
|
= Entity Callbacks
|
|
|
|
The Spring Data infrastructure provides hooks for modifying an entity before and after certain methods are invoked.
|
|
Those so called `EntityCallback` instances provide a convenient way to check and potentially modify an entity in a callback fashioned style. +
|
|
An `EntityCallback` looks pretty much like a specialized `ApplicationListener`.
|
|
Some Spring Data modules publish store specific events (such as `BeforeSaveEvent`) that allow modifying the given entity. In some cases, such as when working with immutable types, these events can cause trouble.
|
|
Also, event publishing relies on `ApplicationEventMulticaster`. If configuring that with an asynchronous `TaskExecutor` it can lead to unpredictable outcomes, as event processing can be forked onto a Thread.
|
|
|
|
Entity callbacks provide integration points with both synchronous and reactive APIs to guarantee in-order execution at well-defined checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
|
|
|
|
Entity callbacks are typically separated by API type. This separation means that a synchronous API considers only synchronous entity callbacks and a reactive implementation considers only reactive entity callbacks.
|
|
|
|
[NOTE]
|
|
====
|
|
The Entity Callback API has been introduced with Spring Data Commons 2.2. It is the recommended way of applying entity modifications.
|
|
Existing store specific `ApplicationEvents` are still published *before* the invoking potentially registered `EntityCallback` instances.
|
|
====
|
|
|
|
[[entity-callbacks.implement]]
|
|
== Implementing Entity Callbacks
|
|
|
|
An `EntityCallback` is directly associated with its domain type through its generic type argument.
|
|
Each Spring Data module typically ships with a set of predefined `EntityCallback` interfaces covering the entity lifecycle.
|
|
|
|
.Anatomy of an `EntityCallback`
|
|
====
|
|
[source,java]
|
|
----
|
|
@FunctionalInterface
|
|
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
|
|
|
|
/**
|
|
* Entity callback method invoked before a domain object is saved.
|
|
* Can return either the same or a modified instance.
|
|
*
|
|
* @return the domain object to be persisted.
|
|
*/
|
|
T onBeforeSave(T entity <2>, String collection <3>); <1>
|
|
}
|
|
----
|
|
<1> `BeforeSaveCallback` specific method to be called before an entity is saved. Returns a potentially modifed instance.
|
|
<2> The entity right before persisting.
|
|
<3> A number of store specific arguments like the _collection_ the entity is persisted to.
|
|
====
|
|
|
|
.Anatomy of a reactive `EntityCallback`
|
|
====
|
|
[source,java]
|
|
----
|
|
@FunctionalInterface
|
|
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
|
|
|
|
/**
|
|
* Entity callback method invoked on subscription, before a domain object is saved.
|
|
* The returned Publisher can emit either the same or a modified instance.
|
|
*
|
|
* @return Publisher emitting the domain object to be persisted.
|
|
*/
|
|
Publisher<T> onBeforeSave(T entity <2>, String collection <3>); <1>
|
|
}
|
|
----
|
|
<1> `BeforeSaveCallback` specific method to be called on subscription, before an entity is saved. Emits a potentially modifed instance.
|
|
<2> The entity right before persisting.
|
|
<3> A number of store specific arguments like the _collection_ the entity is persisted to.
|
|
====
|
|
|
|
NOTE: Optional entity callback parameters are defined by the implementing Spring Data module and inferred from call site of `EntityCallback.callback()`.
|
|
|
|
Implement the interface suiting your application needs like shown in the example below:
|
|
|
|
.Example `BeforeSaveCallback`
|
|
====
|
|
[source,java]
|
|
----
|
|
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered { <2>
|
|
|
|
@Override
|
|
public Object onBeforeSave(Person entity, String collection) { <1>
|
|
|
|
if(collection == "user") {
|
|
return // ...
|
|
}
|
|
|
|
return // ...
|
|
}
|
|
|
|
@Override
|
|
public int getOrder() {
|
|
return 100; <2>
|
|
}
|
|
}
|
|
----
|
|
<1> Callback implementation according to your requirements.
|
|
<2> Potentially order the entity callback if multiple ones for the same domain type exist. Ordering follows lowest precedence.
|
|
====
|
|
|
|
[[entity-callbacks.register]]
|
|
== Registering Entity Callbacks
|
|
|
|
`EntityCallback` beans are picked up by the store specific implementations in case they are registered in the `ApplicationContext`.
|
|
Most template APIs already implement `ApplicationContextAware` and therefore have access to the `ApplicationContext`
|
|
|
|
The following example explains a collection of valid entity callback registrations:
|
|
|
|
.Example `EntityCallback` Bean registration
|
|
====
|
|
[source,java]
|
|
----
|
|
@Order(1) <1>
|
|
@Component
|
|
class First implements BeforeSaveCallback<Person> {
|
|
|
|
@Override
|
|
public Person onBeforeSave(Person person) {
|
|
return // ...
|
|
}
|
|
}
|
|
|
|
@Component
|
|
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
|
|
Ordered { <2>
|
|
|
|
@Override
|
|
public Object onBeforeSave(Person entity, String collection) {
|
|
// ...
|
|
}
|
|
|
|
@Override
|
|
public int getOrder() {
|
|
return 100; <2>
|
|
}
|
|
}
|
|
|
|
@Configuration
|
|
public class EntityCallbackConfiguration {
|
|
|
|
@Bean
|
|
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() { <3>
|
|
return (BeforeSaveCallback<Person>) it -> // ...
|
|
}
|
|
}
|
|
|
|
@Component
|
|
class UserCallbacks implements BeforeConvertCallback<User>,
|
|
BeforeSaveCallback<User> { <4>
|
|
|
|
@Override
|
|
public Person onBeforeConvert(User user) {
|
|
return // ...
|
|
}
|
|
|
|
@Override
|
|
public Person onBeforeSave(User user) {
|
|
return // ...
|
|
}
|
|
}
|
|
----
|
|
<1> `BeforeSaveCallback` receiving its order from the `@Order` annotation.
|
|
<2> `BeforeSaveCallback` receiving its order via the `Ordered` interface implementation.
|
|
<3> `BeforeSaveCallback` using a lambda expression. Unordered by default and invoked last. Note that callbacks implemented by a lambda expression do not expose typing information hence invoking these with a non-assignable entity affects the callback throughput. Use a `class` or `enum` to enable type filtering for the callback bean.
|
|
<4> Combine multiple entity callback interfaces in a single implementation class.
|
|
====
|