Spring Data MongoDB 教程

实体回调

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)
说说我的看法
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号