Files
spring-data-commons/src/main/asciidoc/entity-callbacks.adoc

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.
====