Spring Integration
Kết nối mapping, đặt tên collection và vòng đời reactive của ReactiveMongoTemplate mà không kéo Spring Data MongoDB vào phụ thuộc chính.
Spring Data MongoDB được duy trì trong ứng dụng
build.gradle
textdependencies {
implementation("com.byeolnaerim:reactive-mongo-dsl:1.0.0")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
}Thư viện chính không phụ thuộc trực tiếp vào Spring Data MongoDB. Để sử dụng cấu hình ReactiveMongoTemplate của ứng dụng Spring hiện có, cung cấp adapter MongoExecutionContext ở lớp ứng dụng.
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);
}
}Trong triển khai thực tế, thêm các import Spring/MongoDB cần thiết vào lớp trên. Điều cốt lõi là kết nối việc đọc/ghi entity với MongoConverter, tên collection với ReactiveMongoTemplate, và khởi động phiên với MongoClient tương ứng.
Đăng ký Resolver và 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);
}Phạm vi kết nối từ Adapter
@Document / tên collection
Sử dụng ReactiveMongoTemplate#getCollectionName(...).
@Id / @Field / chuyển đổi tùy chỉnh
Sử dụng MongoConverter để chuyển đổi giữa entity và Document.
Kiểm toán reactive
Kết nối beforePersist(...) với ReactiveBeforeConvertCallback để áp dụng callback kiểm toán reactive trong chuỗi save().
ReactiveAfterSaveCallback
Có thể gọi trong afterPersist(...) sau khi ghi thành công.
Spring QueryMapper
Không áp dụng. Đường dẫn trường DSL String dựa trên tên trường tài liệu MongoDB thực tế.
Spring DataAccessException
Không tự động chuyển đổi. Thao tác thực tế được thực hiện bởi Reactive Streams Driver.
Khi sử dụng @Field
@Field("user_name")
private String userName;
// MongoDB document field 이름을 사용
.fields(pair("user_name", "kim"))Mapping Spring được áp dụng cho việc lưu/truy vấn entity, nhưng đường dẫn String của điều kiện DSL không đi qua QueryMapper của Spring. Tuy nhiên, đoạn đường dẫn id chính xác được chuẩn hóa thành _id theo quy tắc của DSL.