Spring Integration
Spring Data MongoDBをコア依存関係として取り込まずに、ReactiveMongoTemplateのマッピング、コレクション名付け、リアクティブライフサイクルをMongoExecutionContextアダプターに接続します。
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に直接依存しません。既存のSpringアプリケーションのReactiveMongoTemplate設定を使用するには、アプリケーション層でMongoExecutionContextアダプターを提供します。
SpringReactiveMongoExecutionContextアダプター
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のインポートを追加します。重要なのは、エンティティの読み書きを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);
}アダプターで接続されるスコープ
@Document / コレクション名
ReactiveMongoTemplate#getCollectionName(...)を使用します。
@Id / @Field / カスタム変換
エンティティ ↔ Document 変換にMongoConverterを使用します。
リアクティブ監査
beforePersist(...)をReactiveBeforeConvertCallbackに接続すると、save()系列でリアクティブ監査コールバックが適用されます。
ReactiveAfterSaveCallback
書き込み成功後、afterPersist(...)で呼び出すことができます。
Spring QueryMapper
適用されません。DSL文字列フィールドパスは実際のMongoDBドキュメントフィールド名に基づいています。
Spring DataAccessException
自動変換しません。実際の操作はReactive Streams Driverが実行します。
@Fieldを使用する場合
@Field("user_name")
private String userName;
// MongoDB document field 이름을 사용
.fields(pair("user_name", "kim"))エンティティの保存/取得変換にはSpringマッピングが適用されますが、DSL条件の文字列パスはSpring QueryMapperを通過しません。ただし、正確なidパスセグメントはDSL自体の規則により_idに正規化されます。