Spring Integration
Menghubungkan pemetaan, penamaan koleksi, dan siklus hidup reaktif dari ReactiveMongoTemplate tanpa menarik Spring Data MongoDB sebagai ketergantungan inti ke dalam MongoExecutionContext adapter.
Spring Data MongoDB dikelola oleh aplikasi
build.gradle
textdependencies {
implementation("com.byeolnaerim:reactive-mongo-dsl:1.0.0")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
}Perpustakaan inti tidak bergantung langsung pada Spring Data MongoDB. Untuk menggunakan pengaturan ReactiveMongoTemplate dari aplikasi Spring yang ada, lapisan aplikasi menyediakan adapter MongoExecutionContext.
Adapter 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);
}
}Dalam implementasi nyata, tambahkan impor Spring/MongoDB yang diperlukan ke kelas di atas. Intinya adalah menghubungkan pembacaan/penulisan entitas ke MongoConverter, nama koleksi ke ReactiveMongoTemplate, dan memulai sesi ke MongoClient yang sesuai.
Pendaftaran Resolver dan 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);
}Ruang lingkup yang terhubung di Adapter
@Document / nama koleksi
Gunakan ReactiveMongoTemplate#getCollectionName(...).
@Id / @Field / konversi kustom
Gunakan MongoConverter untuk konversi antara entitas ↔ Document.
Audit reaktif
Menghubungkan beforePersist(...) ke ReactiveBeforeConvertCallback akan menerapkan callback audit reaktif pada rangkaian save().
ReactiveAfterSaveCallback
Dapat dipanggil di afterPersist(...) setelah penulisan berhasil.
Spring QueryMapper
Tidak berlaku. Jalur field String DSL berdasarkan nama field dokumen MongoDB yang sebenarnya.
Spring DataAccessException
Tidak melakukan konversi otomatis. Operasi nyata dilakukan oleh Reactive Streams Driver.
Jika menggunakan @Field
@Field("user_name")
private String userName;
// MongoDB document field 이름을 사용
.fields(pair("user_name", "kim"))Mapping Spring diterapkan pada konversi penyimpanan/pencarian entitas, tetapi jalur String dari kondisi DSL tidak melewati QueryMapper Spring. Namun, segmen jalur id yang tepat dinormalisasi menjadi _id berdasarkan aturan DSL itu sendiri.