Skip to content

Multiple MongoTemplate instances pointing to different DBs create entity collections in all DBs [DATAMONGO-1924] #2823

Closed
@spring-projects-issues

Description

@spring-projects-issues

Jean-Pierre Bergamin opened DATAMONGO-1924 and commented

We're trying to use multiple MongoDB databases in our spring boot project. To do that we create two MongoTemplate instances and use the @EnableMongoRepositories annotation to have different repositores in different packages use one of the two MongoTemplates:

@Configuration
public class MongoConfig {
	public static final String A_MONGO_TEMPLATE = "aMongoTemplate";
	public static final String B_MONGO_TEMPLATE = "bMongoTemplate";

	@Configuration
	@EnableMongoRepositories(basePackageClasses = RepoInDbA.class, mongoTemplateRef = A_MONGO_TEMPLATE)
	public static class AMongoConfig {
	}

	@Configuration
	@EnableMongoRepositories(basePackageClasses = RepoInDbB.class, mongoTemplateRef = B_MONGO_TEMPLATE)
	public static class BMongoConfig {
	}

	@Bean(name = {A_MONGO_TEMPLATE, "mongoTemplate"}) // spring boot needs the default "mongoTemplate" bean
	public MongoTemplate aMongoTemplate(MongoConverter converter) throws ClassNotFoundException {
		return new MongoTemplate(new SimpleMongoDbFactory(mongoClient, "database_a"), converter);
	}

	@Bean(name = B_MONGO_TEMPLATE)
	public MongoTemplate bMongoTemplate(MongoConverter converter) throws ClassNotFoundException {
		return new MongoTemplate(new SimpleMongoDbFactory(mongoClient, "database_b"), converter);
	}
}

The problem now is that both MongoTemplate instances create collections for all entities in their DB because indices are created at startup. So both database A and B will contain the entities handled by RepoInDbA and RepoInDbB (if they contain any index annotations). This happens because the c'tor of the MongoTemplate creates a MongoPersistentEntityIndexCreator taking a MongoMappingContext that when setup by spring boot returns all entity classes with it's getPersistentEntities() method (because it's a singleton bean).

To overcome this issue we created a MongoConverterWrapper that returns a different MongoMappingContext instance per case:

	public class MongoConverterWrapper implements MongoConverter {
		private final MongoConverter mongoConverter;

		private final MongoMappingContext mongoMappingContext;

		public MongoConverterWrapper(MongoConverter mongoConverter, Package pkg) throws ClassNotFoundException {
			this.mongoConverter = mongoConverter;

			// Same as org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.mongoMappingContext
			// but....
			MongoMappingContext context = new MongoMappingContext();
			final Set<Class<?>> initialEntitySet = new EntityScanner(applicationContext)
				.scan(Document.class, Persistent.class);

			// ... we remove the entity classes that don't belong to the given package.
			initialEntitySet.removeIf(clazz -> !clazz.getPackage().getName().startsWith(pkg.getName()));

			context.setInitialEntitySet(initialEntitySet);
			Class<?> strategyClass = mongoProperties.getFieldNamingStrategy();
			if (strategyClass != null) {
				context.setFieldNamingStrategy(
					(FieldNamingStrategy) BeanUtils.instantiate(strategyClass));
			}
			context.setSimpleTypeHolder(customConversions().getSimpleTypeHolder());
			context.initialize();
			this.mongoMappingContext = context;
		}


		@Override
		public MongoTypeMapper getTypeMapper() {
			return mongoConverter.getTypeMapper();
		}

		@Override
		public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
			return mongoMappingContext;
		}

		@Override
		public ConversionService getConversionService() {
			return mongoConverter.getConversionService();
		}

		@Override
		public <R> R read(Class<R> type, DBObject source) {
			return mongoConverter.read(type, source);
		}

		@Override
		public Object convertToMongoType(Object obj) {
			return mongoConverter.convertToMongoType(obj);
		}

		@Override
		public Object convertToMongoType(Object obj, TypeInformation<?> typeInformation) {
			return mongoConverter.convertToMongoType(obj, typeInformation);
		}

		@Override
		public DBRef toDBRef(Object object, MongoPersistentProperty referingProperty) {
			return mongoConverter.toDBRef(object, referingProperty);
		}

		@Override
		public void write(Object source, DBObject sink) {
			mongoConverter.write(source, sink);
		}
	}

So we create the MongoTemplate now like:

@Bean(name = B_MONGO_TEMPLATE)
public MongoTemplate bMongoTemplate(MongoConverter converter) throws ClassNotFoundException {
     return new MongoTemplate(new SimpleMongoDbFactory(mongoClient, "database_b"), new MongoConverterWrapper(converter, RepoInDbA.class.getPackage()));
}

What could be a way to make that easier? I'm pretty sure it's a very common use case that people want to access multiple databases. Maybe I'm also missing just something?

Should e.g. spring boot automatically create a mongo template with the given name if @EnableMongoRepositories(basePackageClasses = RepoInDbB.class, mongoTemplateRef = B_MONGO_TEMPLATE) is used somewhere so that the created MongoTemplate only handles entities in its base package?

Should you somehow make it easier to tell the MongoTemplate to only handle entities in a certain package?


No further details from DATAMONGO-1924

Metadata

Metadata

Assignees

Labels

status: declinedA suggestion or change that we don't feel we should currently applytype: bugA general bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions