Spring Integration

Conecta el mapeo, la nomenclatura de colecciones y el ciclo de vida reactivo de ReactiveMongoTemplate al adaptador MongoExecutionContext sin arrastrar Spring Data MongoDB como dependencia principal.


Spring Data MongoDB es mantenido por la aplicación.

build.gradle
text
dependencies {
  implementation("com.byeolnaerim:reactive-mongo-dsl:1.0.0")
  implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
}

La biblioteca principal no depende directamente de Spring Data MongoDB. Para usar la configuración de ReactiveMongoTemplate de aplicaciones Spring existentes, se proporciona un adaptador MongoExecutionContext en la capa de aplicación.

Adaptador SpringReactiveMongoExecutionContext

SpringReactiveMongoExecutionContext.java
java
public 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);
  }
}

En la implementación real, se añaden las importaciones necesarias de Spring/MongoDB a la clase anterior. La clave es conectar la lectura/escritura de entidades a MongoConverter, el nombre de la colección a ReactiveMongoTemplate, y el inicio de sesión al MongoClient correspondiente.

Registro de Resolver y Bean DSL

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);
}

Alcance conectado en el adaptador

@Document / nombre de colección

Se utiliza ReactiveMongoTemplate#getCollectionName(...).

@Id / @Field / conversiones personalizadas

Se utiliza MongoConverter para la conversión entre entidad ↔ Document.

Auditoría reactiva

Al conectar beforePersist(...) a ReactiveBeforeConvertCallback, se aplica el callback de auditoría reactiva en la serie de save().

ReactiveAfterSaveCallback

Se puede llamar en afterPersist(...) después de un éxito de escritura.

Spring QueryMapper

No se aplica. La ruta del campo de cadena DSL se basa en el nombre del campo del documento MongoDB real.

Spring DataAccessException

No se convierte automáticamente. La operación real es realizada por el Reactive Streams Driver.

Cuando se utiliza @Field

java
@Field("user_name")
private String userName;

// MongoDB document field 이름을 사용
.fields(pair("user_name", "kim"))

La conversión de almacenamiento/búsqueda de entidades aplica el mapeo de Spring, pero la ruta de cadena de las condiciones DSL no pasa por QueryMapper de Spring. Sin embargo, el segmento de ruta id exacto se normaliza a _id según las reglas del propio DSL.

La transacción de Spring y la transacción DSL son separadas.

© 2026 Byeolnaerim. Todos los derechos reservados.IntroducciónPolítica de privacidad