Search code examples
springmongodbspring-dataspring-data-mongodb

Can Spring Data MongoDB be configured to support a different database for each repository?


I've been struggling for the past week to successfully integrate Spring Data MongoDB into our application. We use the fairly common practice of having separate databases for each collection that we rely on. For instance, TenantConfiguration database contains only the TenantConfigurations collection.

I've read through the documentation several times and trawled through the code for a solution but have turned up nothing. Surely such a widely adopted project has some solution for this issue? My current attempt looks like this:

@Configuration
@EnableMongoRepositories(basePackages = "com.whatever.service.repository",
        basePackageClasses = TenantConfigurationRepository.class,
        mongoTemplateRef = "tenantConfigurationTemplate")
public class TenantConfigurationRepositoryConfig {

    @Value("${mongo.hosts}")
    private List<String> mongoHosts;

    @Bean
    public MongoTemplate tenantConfigurationTemplate() throws Exception {
        final List<ServerAddress> serverAddresses = new ArrayList<>();
        for (String host : mongoHosts) {
            serverAddresses.add(new ServerAddress(host, 27017));
        }

        final MongoClientOptions clientOptions = new MongoClientOptions.Builder()
                .connectTimeout(25000)
                .readPreference(ReadPreference.primaryPreferred())
                .build();

        final MongoClient client = new MongoClient(serverAddresses, clientOptions);
        return new MongoTemplate(client, "TenantConfiguration");
    }
}

Here is one of the other individual repository configurations:

@Configuration
@EnableMongoRepositories(basePackages = "com.whatever.service.repository",
        basePackageClasses = RegisteredCardRepository.class,
        mongoTemplateRef = "registeredCardTemplate")
public class RegisteredCardRepositoryConfig {

    @Value("${mongo.hosts}")
    private List<String> mongoHosts;

    @Bean
    public MongoTemplate registeredCardTemplate() throws Exception {
        final List<ServerAddress> serverAddresses = new ArrayList<>();
        for (String host : mongoHosts) {
            serverAddresses.add(new ServerAddress(host, 27017));
        }

        final MongoClientOptions clientOptions = new MongoClientOptions.Builder()
                .connectTimeout(25000)
                .readPreference(ReadPreference.primaryPreferred())
                .build();

        final MongoClient client = new MongoClient(serverAddresses, clientOptions);
        return new MongoTemplate(client, "RegisteredCard");
    }
}

Now here is the actual repository definition for the RegisteredCard repository:

@Repository
public interface RegisteredCardRepository extends MongoRepository<RegisteredCard, Guid>,
        QueryDslPredicateExecutor<RegisteredCard> { }

This all makes perfect sense to me, the individual configurations uniquely identify the specific repository interfaces they configure and the specific template bean to use with that repository via the mongoTemplateRef parameter of the annotation. At least, this is how the documentation seems to imply it should work.

In reality, when I start up the application, the RegisteredCard repository resolves to a MongoDB repository instance with an associated MongoDbFactory that is bound to the TenantConfiguration database. In fact, every single repository receives the same, incorrect MongoOperations object. Despite each repository having its own unique configuration, it appears that whatever database is accessed first remains the target database for every repository.

Are there any solutions available to this problem?


Solution

  • It's taken me almost a week, but I've actually found a passable solution to this issue. Here's a quick run-down of facts I've picked up while researching this issue:

    • @EnableMongoRepositories(basePackageClasses = Whatever.class) simply uses a qualified class name to indicate what package it should scan for all of your defined data models. This is entirely equivalent to doing @EnableMongoRepositories(basePackageClasses = "com.mypackage.whatevers") if Whatever.class resides in that package.
    • @EnableMongoRepositories is not repeatable but can be used to annotate several classes. This has been covered in other SO conversations but bears repeating here. You will need to define several repository configuration classes; one for each database you intend to interact with.
    • Each of your individual repository configurations must specify its own MongoTemplate instance in the @EnableMongoRepositories annotation. You can get away with providing only a single Mongo bean but the MongoTemplate relies on a specific MongoMappingContext.
    • The @EnableMongoRepositories annotation helps define your mapping context, this understands the structure of your data models and how to serialize them. It also understands the @Document and @Field annotations and does the heavy lifting of persisting your objects. The Mongo template instances are where your specify what database you want to interact with. So by providing the @EnableMongoRepositories annotation with both a basePackage attribute and a mongoTemplateRef attribute you can tell Spring Data Mongo to "take these models and persist them in this specific database".

    The unfortunate requirement for this solution is that you must organize your data models into separate packages depending on what database they belong in. If, like me, you are using a Mongo database structure that allocates a single collection to each database (this is fairly common for heavily accessed collections), this means that each of your data models must reside in its own package. Each of these packages must be pointed to by an @EnableMongoRepositories annotation also containing a mongoTemplateRef attribute to a unique MongoTemplate bean.

    I hope this helps someone avoid the trouble I've gone through trying to accomplish what should be a fairly run-of-the-mill Mongo integration.

    PS: Abandon all hope, those who seek to combine auditing with this configuration.