Multiple databases with Spring Boot MongoDB Repositories

Recently I needed to setup my Spring-Boot app to manage 2 instances of the Mongo driver. This allowed me to configure each one with database specific settings like the ReadPreference example below.

My requirements:

  1. I wanted all Spring configuration in Java code/annotations. (I hate XML!)
  2. I wanted Spring to manage both the Mongo and MongoTemplate objects.
  3. I wanted to use MongoRepository Interface beans and have the correct MongoTemplate back it.

The first thing we need to do is create a base configuration that will contain the generic beans shared between our two databases.

MongoConfiguration.java

@Configuration
public class MongoConfiguration {
    /**
     * For a clustered Mongo environment we would want to load multiple 
     * hosts. This will work if we use a single host or clustered.
     *
     * If the mongo.hosts key could not be found defaults to localhost
     *
     * One of the following would exist in our Spring properties file
     * 1) mongo.hosts = localhost
     * 2) mongo.hosts = 10.1.1.1,10.1.1.2,10.1.1.3
     */
    @Value("#{'${mongo.hosts:localhost}'.split(',')}")
    private List<String> hosts;

    /**
     * The port our Mongo hosts are running on.
     * Defaults to 27017
     */
    @Value("${mongo.port:27017}")
    private int port;

    /**
     * Creates a base Mongo instance that can be configured for each 
     * implementation.
     *
     * NOTE: If you are trying to connect to multiple MongoDB's then 
     * you would want to create 2 instances of this method as beans 
     * loading the correct mongo hosts. For my implementation I just 
     * wanted different global configurations pointed at the same 
     * database.
     *
     * @return A generic Mongo instance pointed at the hosts.
     * @throws Exception
     */
    private Mongo createMongo() throws Exception {
        final List<ServerAddress> serverList = new ArrayList<>();
        for (final String host : hosts) {
            serverList.add(new ServerAddress(host, port));
        }
        
        // MongoClientOptions would be created here and passed into 
        // the MongoClient as it's second param.
        return new MongoClient(serverList);
    }
    
    @Primary
    @Bean
    public Mongo readFromSecondaryNodeMongo() {
        final Mongo mongo = createMongo();
        // Do custom global configuration
        mongo.setReadPreference(ReadPreference.secondaryPreferred());
        return mongo;
    }
    
    @Bean
    public Mongo readFromPrimaryNodeMongo() {
        final Mongo mongo = createMongo();
        mongo.setReadPreference(ReadPreference.primaryPreferred());
        return mongo;
    }

    /**
     * This is the default DB Factory and will have the 
     * readFromSecondaryNodeMongo() bean injected due to the @Primary 
     * annotation
     * 
     * @param mongo auto injected using the @Primary bean
     * @return a new MongoDbFactory
     */
    @Bean
    public MongoDbFactory mongoDbFactory(Mongo mongo) {
        return new SimpleMongoDbFactory(mongo, "DatabaseName");
    }
}

Now that we have beans defined we need to create the configurations that will define and load the MongoTemplates into the correct repository interfaces.

ReadPrimaryNodeConfiguration.java

/**
 * The basePackages needs to point to the Interfaces
 * this configuration is going to back.
 */
@Configuration
@EnableMongoRepositories(
        basePackages = "com.example.primary",
        mongoTemplateRef = "readPrimaryNodeTemplate"
)
@Import(MongoConfiguration.class)
public class ReadPrimaryNodeConfiguration {
    /**
     * The @Qualifier here so we can load the non-default
     * Mongo bean.
     * 
     * @param mongo the non-default Mongo instance
     * @return a new MongoTemplate
     * @throws Exception thrown from MongoTemplate
     */
    @Bean
    public MongoTemplate readPrimaryNodeTemplate(
            @Qualifier("readFromPrimaryNodeMongo") Mongo mongo
    ) throws Exception {
        final MongoDbFactory factory = 
                new SimpleMongoDbFactory(mongo, "DatabaseName");
        return new MongoTemplate(factory);
    }
}

ReadSecondaryNodeConfiguration.java

/**
 * The basePackages needs to point to the Interfaces
 * this configuration is going to back.
 */
@Configuration
@EnableMongoRepositories(
        basePackages = "com.example.secondary",
        mongoTemplateRef = "readSecondaryNodeTemplate"
)
@Import(MongoConfiguration.class)
public class ReadSecondaryNodeConfiguration {
    /**
     * We don't use an @Qualifier here so the default 
     * MongoDbFactory in MongoConfiguration will be injected
     * 
     * @param factory the default factory
     * @return a new MongoTemplate
     * @throws Exception thrown from MongoTemplate
     */
    @Primary
    @Bean
    public MongoTemplate readSecondaryNodeTemplate(
            MongoDbFactory factory) throws Exception {
        return new MongoTemplate(factory);
    }
}

With that all we need to do is create the repository interface beans in the correct base package.

This will be backed by the MongoTemplate created in the ReadPrimaryNodeConfiguration.java

com.example.primary.ReadPrimaryNodeRepository.java

@Repository
public interface ReadPrimaryNodeRepository 
        extends MongoRepository<MyDocument, String> {
    // Method to look up a document by its ID
    MyDocument findById(String id);
}

And this one will be backed by the MongoTemplate created in ReadSecondaryNodeConfiguration.java

com.example.secondary.ReadSecondaryNodeRepository.java

@Repository
public interface ReadSecondaryNodeRepository 
        extends MongoRepository<MyDocument, String> {
    // Method to look up a document by its ID
    MyDocument findById(String id);
}