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)