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:
- I wanted all Spring configuration in Java code/annotations. (I hate XML!)
- I wanted Spring to manage both the Mongo and MongoTemplate objects.
- 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); }