Spring Integration
เชื่อมโยงการแมพปิ้ง, การตั้งชื่อคอลเลกชัน และวงจรชีวิตเชิงปฏิกิริยาของ ReactiveMongoTemplate กับ MongoExecutionContext adapter โดยไม่ดึง Spring Data MongoDB มาเป็นการพึ่งพาหลัก
Spring Data MongoDB จะถูกดูแลในแอปพลิเคชัน
build.gradle
textdependencies {
implementation("com.byeolnaerim:reactive-mongo-dsl:1.0.0")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
}ไลบรารีหลักไม่พึ่งพา Spring Data MongoDB โดยตรง หากต้องการใช้การตั้งค่า ReactiveMongoTemplate ของแอปพลิเคชัน Spring ที่มีอยู่ จะต้องมี MongoExecutionContext adapter ในเลเยอร์แอปพลิเคชัน
SpringReactiveMongoExecutionContext adapter
SpringReactiveMongoExecutionContext.java
javapublic class SpringReactiveMongoExecutionContext implements MongoExecutionContext {
private final ReactiveMongoTemplate reactiveMongoTemplate;
private final MongoClient mongoClient;
private final MongoConverter mongoConverter;
private final ReactiveEntityCallbacks entityCallbacks;
public SpringReactiveMongoExecutionContext(
ReactiveMongoTemplate reactiveMongoTemplate,
MongoClient mongoClient,
ReactiveEntityCallbacks entityCallbacks
) {
this.reactiveMongoTemplate = Objects.requireNonNull(reactiveMongoTemplate);
this.mongoClient = Objects.requireNonNull(mongoClient);
this.mongoConverter = reactiveMongoTemplate.getConverter();
this.entityCallbacks = Objects.requireNonNull(entityCallbacks);
}
@Override
public Mono<MongoDatabase> getDatabase() {
return reactiveMongoTemplate.getMongoDatabase();
}
@Override
public Mono<ClientSession> startSession() {
return Mono.from(mongoClient.startSession());
}
@Override
public String getCollectionName(Class<?> entityClass) {
return reactiveMongoTemplate.getCollectionName(entityClass);
}
@Override
public Document write(Object source) {
Document document = new Document();
mongoConverter.write(source, document);
return document;
}
@Override
public <T> T read(Class<T> targetType, Document source) {
return mongoConverter.read(targetType, source);
}
@Override
public Object getId(Object entity) {
MongoPersistentEntity<?> persistentEntity =
mongoConverter.getMappingContext().getPersistentEntity(entity.getClass());
if (persistentEntity == null || !persistentEntity.hasIdProperty()) {
return null;
}
Object id = persistentEntity.getIdentifierAccessor(entity).getIdentifier();
if (id == null) return null;
Document document = new Document();
mongoConverter.write(entity, document);
return document.get("_id");
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setId(Object entity, Object id) {
MongoPersistentEntity persistentEntity =
mongoConverter.getMappingContext().getPersistentEntity(entity.getClass());
if (persistentEntity == null || !persistentEntity.hasIdProperty()) return;
MongoPersistentProperty idProperty =
(MongoPersistentProperty) persistentEntity.getRequiredIdProperty();
Object targetId = id;
if (id instanceof ObjectId objectId && idProperty.getType() == String.class) {
targetId = objectId.toHexString();
} else if (
id != null
&& !idProperty.getType().isInstance(id)
&& mongoConverter.getConversionService()
.canConvert(id.getClass(), idProperty.getType())
) {
targetId = mongoConverter.getConversionService()
.convert(id, idProperty.getType());
}
PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(entity);
accessor.setProperty(idProperty, targetId);
}
@Override
public <T> Mono<T> beforePersist(T entity, String collectionName) {
return entityCallbacks.callback(
ReactiveBeforeConvertCallback.class,
entity,
collectionName
);
}
@Override
public <T> Mono<T> afterPersist(
T entity,
Document document,
String collectionName
) {
return entityCallbacks.callback(
ReactiveAfterSaveCallback.class,
entity,
document,
collectionName
);
}
@Override
public Object getSessionScope() {
return mongoClient;
}
@Override
public Object getNative() {
return reactiveMongoTemplate;
}
@Override
public <T> T getNative(Class<T> nativeType) {
if (nativeType.isInstance(reactiveMongoTemplate)) {
return nativeType.cast(reactiveMongoTemplate);
}
if (nativeType.isInstance(mongoClient)) {
return nativeType.cast(mongoClient);
}
return MongoExecutionContext.super.getNative(nativeType);
}
}ในการใช้งานจริงจะต้องเพิ่มการนำเข้า Spring/MongoDB ที่จำเป็นในคลาสข้างต้น จุดสำคัญคือการเชื่อมโยงการอ่าน/เขียน entity กับ MongoConverter, ชื่อคอลเลกชันกับ ReactiveMongoTemplate, และการเริ่มต้นเซสชันกับ MongoClient ที่เกี่ยวข้อง
การลงทะเบียน Resolver และ DSL Bean
MongoTemplateNameResolver.java
java@Component
public class MongoTemplateNameResolver
implements MongoTemplateResolver<MongoTemplateName> {
private final MongoExecutionContext frontExecutionContext;
private final MongoExecutionContext backExecutionContext;
public MongoTemplateNameResolver(
@Qualifier("frontMongoTemplate") ReactiveMongoTemplate frontTemplate,
@Qualifier("frontMongoClient") MongoClient frontMongoClient,
@Qualifier("backMongoTemplate") ReactiveMongoTemplate backTemplate,
@Qualifier("backMongoClient") MongoClient backMongoClient,
ApplicationContext applicationContext
) {
ReactiveEntityCallbacks callbacks =
ReactiveEntityCallbacks.create(applicationContext);
this.frontExecutionContext = new SpringReactiveMongoExecutionContext(
frontTemplate, frontMongoClient, callbacks
);
this.backExecutionContext = new SpringReactiveMongoExecutionContext(
backTemplate, backMongoClient, callbacks
);
}
@Override
public MongoExecutionContext getTemplate(MongoTemplateName key) {
return switch (key) {
case FRONT -> frontExecutionContext;
case BACK -> backExecutionContext;
};
}
}ReactiveMongoDslConfiguration.java
java@Bean
public ReactiveMongoDsl<MongoTemplateName> reactiveMongoDsl(
MongoTemplateResolver<MongoTemplateName> resolver
) {
return new ReactiveMongoDsl<>(resolver);
}ขอบเขตที่เชื่อมต่อใน Adapter
@Document / ชื่อคอลเลกชัน
ใช้ ReactiveMongoTemplate#getCollectionName(...)
@Id / @Field / การแปลงแบบกำหนดเอง
ใช้ MongoConverter ในการแปลง entity ↔ Document
การตรวจสอบเชิงปฏิกิริยา
เชื่อมโยง beforePersist(...) กับ ReactiveBeforeConvertCallback จะทำให้มีการใช้ callback การตรวจสอบเชิงปฏิกิริยาในกลุ่ม save()
ReactiveAfterSaveCallback
สามารถเรียกใช้ได้ใน afterPersist(...) หลังจากการเขียนสำเร็จ
Spring QueryMapper
ไม่ถูกนำไปใช้ เส้นทางฟิลด์ DSL String จะอิงตามชื่อฟิลด์เอกสาร MongoDB จริง
Spring DataAccessException
ไม่ทำการแปลงอัตโนมัติ การดำเนินการจริงจะดำเนินการโดย Reactive Streams Driver
เมื่อใช้ @Field
@Field("user_name")
private String userName;
// MongoDB document field 이름을 사용
.fields(pair("user_name", "kim"))การแปลงการบันทึก/ค้นหา entity จะใช้การแมพปิ้งของ Spring แต่เส้นทาง String ของเงื่อนไข DSL จะไม่ผ่าน QueryMapper ของ Spring อย่างไรก็ตาม ส่วนที่แน่นอนของเส้นทาง id จะถูกทำให้เป็นมาตรฐานเป็น _id ตามกฎของ DSL เอง