Spring Data 基础设施提供了钩子,用于在某些方法被调用之前和之后修改实体。这些所谓的 EntityCallback 实例提供了一种方便的方式,可以以回调的方式检查和修改一个实体。
EntityCallback 看起来很像一个专门的 ApplicationListener。一些 Spring Data 模块发布了特定的存储事件(比如 BeforeSaveEvent),允许修改给定的实体。在某些情况下,例如在处理不可变类型时,这些事件会造成麻烦。另外,事件发布依赖于 ApplicationEventMulticaster。如果将其与异步 TaskExecutor 配置在一起,可能会导致不可预测的结果,因为事件处理可以分叉(fork)到一个线程。
实体回调提供了同步和响应式 API 的集成方式,以保证在处理链中定义好的检查点上的无序执行,返回可能修改的实体或响应式包装类型。实体回调通常由 API 类型分开,这种分离意味着 同步 API 只考虑同步的实体回调,而响应式实现只考虑响应式实体回调。
EntityCallback 通过其泛型类型参数直接与其域类型关联。每个 Spring 数据模块通常附带一组预定义的实体回调接口,涵盖实体生命周期。实体回调剖析如下:
@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, String collection);
}其中,onBeforeSave() 方法是 BeforeSaveCallback 类的具体方法。在实体被保存之前,onBeforeSave() 将被调用,该方法将返回一个可能被修改的实例。entity 参数表示在持久化之前的实体。collection 参数表示一些特定的存储参数,比如实体被持久化到的集合等等。
响应式实体回调剖析如下:
@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, String collection);
}其中,onBeforeSave() 方法是 BeforeSaveCallback 类的特定方法,在实体被保存之前被调用,该方法将返回一个可能被修改的实例。entity 表示在持久化之前的实体。collection 表示一些特定的存储参数,比如实体被持久化到的集合等。
实现适合你应用需求的接口,如下面所示:
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)根据你的需求实现回调方法。
(2)如果同一领域类型存在多个实体回调,则可能对其进行排序,排序遵循最低优先级。
下面的例子解释了一个有效的实体回调注册的集合:
(1)BeforeSaveCallback 从 @Order 注解中接收其顺序。如下:
@Order(1)
@Component
class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}(2)BeforeSaveCallback 通过 Ordered 接口的实现接收其顺序。如下:
@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered {
@Override
public Object onBeforeSave(Person entity, String collection) {
// ...
}
@Override
public int getOrder() {
return 100;
}
}(3)BeforeSaveCallback 使用一个 lambda 表达式。默认情况下未排序,最后调用。请注意,由 lambda 表达式实现的回调不暴露类型信息,因此用一个不可分配的实体调用这些回调会影响回调的吞吐量。使用一个类或枚举来启用回调 Bean 的类型过滤。如下:
@Configuration
public class EntityCallbackConfiguration {
@Bean
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() {
return (BeforeSaveCallback<Person>) it -> // ...
}
}(4)将多个实体回调接口合并到一个实现类中。如下:
@Component
class UserCallbacks implements BeforeConvertCallback<User>, BeforeSaveCallback<User> {
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}(1)application.properties 配置文件
# Log logging.level.root=debug # MongoDB spring.data.mongodb.uri=mongodb://localhost:27017/test
(2)AppConfig.java 配置类,配置 MongoTemplate 对象
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
/**
* 配置 MongoTemplate
* @author hxstrive.com 2022/12/23
*/
@Slf4j
@Configuration
public class AppConfig {
@Bean
public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) {
log.info("mongoTemplate({}, {})", mongoDatabaseFactory);
return new MongoTemplate(mongoDatabaseFactory);
}
}(3)集合 person 对应的实体
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
@Builder
public class Person {
/** 用户ID */
private int id;
/** 用户姓名 */
private String name;
/** 年龄 */
private int age;
}(4)自定义实体回调
a、实体保存前回调,即在实体被保存前触发。
import com.hxstrive.springdata.mongodb.entity.Person;
import org.bson.Document;
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveCallback;
import org.springframework.stereotype.Component;
/**
* 自定义 BeforeSaveCallback
* @author hxstrive.com
*/
@Component
public class MyBeforeSaveCallback implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person entity, Document document, String collection) {
System.out.println("onBeforeSave()");
System.out.println("entity = " + entity);
System.out.println("document = " + document);
System.out.println("collection = " + collection);
// 修改名称,不会生效,不会写入到 MongoDB 数据库
entity.setName("new value");
// 修改名称,会写入到 MongoDB 数据库
document.append("name", "new value2");
return entity;
}
}b、实体保存后回调,即在实体被保存后触发。
import com.hxstrive.springdata.mongodb.entity.Person;
import org.bson.Document;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
import org.springframework.stereotype.Component;
/**
* 扩展 AfterSaveCallback
* @author hxstrive.com
*/
@Component
public class MyAfterSaveCallback implements AfterSaveCallback<Person> {
@Override
public Person onAfterSave(Person entity, Document document, String collection) {
System.out.println("onAfterSave()");
System.out.println("entity = " + entity);
System.out.println("document = " + document);
System.out.println("collection = " + collection);
return entity;
}
}(5)客户端代码,说明见注释。
import com.hxstrive.springdata.mongodb.entity.Person;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import java.util.List;
@SpringBootTest
class ListenerDemo2 {
@Autowired
private MongoTemplate mongoTemplate;
@BeforeEach
public void init() {
mongoTemplate.dropCollection(Person.class);
}
@Test
void save() {
// 保存文档
mongoTemplate.save(Person.builder().id(100).name("Helen").age(28).build());
// 获取文档
List<Person> list = mongoTemplate.find(Query.query(Criteria.where("id").is(100)), Person.class);
if(list.size() == 1) {
System.out.println(list.get(0));
// 结果:
// Person(id=100, name=new value2, age=28)
}
}
}运行客户端代码,输出如下:
onBeforeSave() # 保存前回调信息
entity = Person(id=100, name=Helen, age=28)
document = Document{{_id=100, name=Helen, age=28, _class=com.hxstrive.springdata.mongodb.entity.Person}}
collection = person
onAfterSave() # 保存后回调信息
entity = Person(id=100, name=new value, age=28)
document = Document{{_id=100, name=new value2, age=28, _class=com.hxstrive.springdata.mongodb.entity.Person}}
collection = person
Person(id=100, name=new value2, age=28)