Using multiple datasources of MongoDB with Spring Boot and Spring Data - Walking Techie

Blog about Java programming, Design Pattern, and Data Structure.

Saturday, July 8, 2017

Using multiple datasources of MongoDB with Spring Boot and Spring Data

Spring Boot with Spring Data makes it easy to access a database through so called Repositories. But what if you want to access multiple databases maybe even with different Database Management Systems?

In this post, we will show you how to configure multi data source with the mongoDB using spring boot and spring data.

Project structure

This is a directory structure of the standard gradle project.

MongoDB multi data source example project structure

Project dependencies

task wrapper(type: Wrapper) {
    gradleVersion = '3.4.1'
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'


tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

buildscript {
    ext {
        springBootVersion = '1.5.4.RELEASE'
    }
    repositories {
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

repositories {
    mavenLocal()
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web:1.4.3.RELEASE') {
        exclude module: "spring-boot-starter-tomcat"
    }
    compile 'org.springframework.boot:spring-boot-starter-jetty:1.4.3.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter-data-mongodb:1.5.2.RELEASE'
    compileOnly 'org.projectlombok:lombok:1.16.12'
    testCompile('org.springframework.boot:spring-boot-starter-test') {
        exclude(module: 'commons-logging')
    }
}

application.properties file

#mongo db configuration details
primary.mongodb.host=127.0.0.1
primary.mongodb.port=27017
primary.mongodb.database=test
secondary.mongodb.host=127.0.0.1
secondary.mongodb.port=27017
secondary.mongodb.database=springbatch

Create AbstractMongoConfig class for MongoDB

This class will have the details of database/datasource like hostname, port, and database.

package com.walking.techie.mongo.config;

import com.mongodb.MongoClient;
import lombok.Data;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;

@Data
public abstract class AbstractMongoConfig {

  //Mongo DB Properties
  private String host, database;
  private int port;

  /*
   * Method that creates MongoDbFactory
   * Common to both of the MongoDb connections
   */
  public MongoDbFactory mongoDbFactory() {
    return new SimpleMongoDbFactory(getMongoClient(), database);
  }

  /*
   * Method that creates MongoClient
   */
  private MongoClient getMongoClient() {
    return new MongoClient(host, port);
  }

  /*
   * Factory method to create the MongoTemplate
   */
  abstract public MongoTemplate getMongoTemplate();
}

Create configurable connection class for MongoDB

Here we will create two mongo configuration class, one configuration class will act as the primary data source and other will act as the secondary data source. Both classes will extends AbstractMongoConfig class.

package com.walking.techie.mongo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@Configuration
@EnableMongoRepositories(basePackages = {"com.walking.techie.common.repository",
    "com.walking.techie.primary.repository"}, mongoTemplateRef = "primaryMongoTemplate")
@ConfigurationProperties(prefix = "primary.mongodb")
public class PrimaryMongoConnection extends AbstractMongoConfig {

  /**
   * Implementation of the MongoTemplate factory method
   *
   * @Bean gives a name (primaryMongoTemplate) to the created MongoTemplate instance
   * @Primary declares that if MongoTemplate is autowired without providing a specific name, this is
   * the instance which will be mapped by default
   */

  @Primary
  @Override
  @Bean(name = "primaryMongoTemplate")
  public MongoTemplate getMongoTemplate() {
    return new MongoTemplate(mongoDbFactory());
  }
}
package com.walking.techie.mongo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@Configuration
@EnableMongoRepositories(
    basePackages = {"com.walking.techie.secondary.repository"},
    mongoTemplateRef = "secondaryMongoTemplate"
)
@ConfigurationProperties(prefix = "secondary.mongodb")
public class SecondaryMongoConnection extends AbstractMongoConfig {

  /**
   * Implementation of the MongoTemplate factory method
   *
   * @Bean gives a name (secondaryMongoTemplate) to the created MongoTemplate instance Note that
   * this method doesn't have @Primary
   */

  @Override
  public
  @Bean(name = "secondaryMongoTemplate")
  MongoTemplate getMongoTemplate() {
    return new MongoTemplate(mongoDbFactory());
  }
}

Create repository

Here creating three repository, in that two repository will use the primary mongo connection, and other will use secondary mongo connection. I have created three packages to have all the repository in separate package to show the flexibility of the configurable connection.

package com.walking.techie.common.repository;

import com.walking.techie.model.User;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface UserRepository extends MongoRepository<User, String> {

}
package com.walking.techie.primary.repository;

import com.walking.techie.model.Employee;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface EmployeeRepository extends MongoRepository<Employee, String> {

}
package com.walking.techie.secondary.repository;


import com.walking.techie.model.Product;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface ProductRepository extends MongoRepository<Product, String> {

}

Java model class

package com.walking.techie.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "user")
@Data
public class User {

  @Id
  private String id;

  private String name;
  private String email;
  @Indexed
  private int age;
}
package com.walking.techie.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "employee")
@Data
public class Employee {

  @Id
  private String id;

  private String name;
  private String email;
  @Indexed
  private int age;
}
package com.walking.techie.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "product")
@Data
public class Product {

  @Id
  private String id;

  private String name;
  @Indexed
  private int price;
}

Controller

Here, create a rest controller.

package com.walking.techie.controller;

import com.walking.techie.handler.SaveTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("save")
public class Transaction {

  @Autowired
  private SaveTransaction saveTransaction;

  @PostMapping
  public String save() {
    saveTransaction.save();
    return "success";
  }
}

Handler

Handler will have the logic to store the data into the mongoDB.

package com.walking.techie.handler;

public interface SaveTransaction {

  public void save();
}
package com.walking.techie.handler;

import com.walking.techie.common.repository.UserRepository;
import com.walking.techie.model.Employee;
import com.walking.techie.model.Product;
import com.walking.techie.model.User;
import com.walking.techie.primary.repository.EmployeeRepository;
import com.walking.techie.secondary.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SaveTransactionHandler implements SaveTransaction {

  @Autowired
  private UserRepository userRepository;
  @Autowired
  private ProductRepository productRepository;
  @Autowired
  private EmployeeRepository employeeRepository;

  @Override
  public void save() {
    //user details
    User user = new User();
    user.setName("John");
    user.setAge(28);
    user.setEmail("john@gmail.com");

    // product details
    Product product = new Product();
    product.setName("Insurance");
    product.setPrice(10000);

    //Employee details
    Employee employee = new Employee();
    employee.setName("Walking Techie");
    employee.setAge(26);
    employee.setEmail("walkingtechie@gmail.com");

    //save in DB
    userRepository.save(user);
    productRepository.save(product);
    employeeRepository.save(employee);
  }
}

Run Application

package com.walking.techie;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Output

Run the web application it will deploy on the embedded jetty server on port 8080. When you call the following url with post method you will get the success as response, after that you can verify in the mongoDB.

localhost:8080/save

Note : This code has been compiled and run on mac notebook and intellij IDEA.

7 comments :



  1. Thanks for sharing sir,the useful information which is very informative and good points were stated in this article.for more information please visit our website.
    MongoDB Training in Hyderabad

    ReplyDelete
  2. how do i use @CreatedDate with this example ... we need @EnableMongoAuditing ?
    if i use this @EnableMongoAuditing annotation then i found an error
    Parameter 0 of constructor in org.springframework.data.mongodb.config.MongoAuditingRegistrar$MongoMappingContextLookup required a bean of type 'org.springframework.data.mongodb.core.convert.MappingMongoConverter' that could not be found.

    The following candidates were found but could not be injected:
    - Bean method 'mappingMongoConverter' in 'MongoDataAutoConfiguration' not loaded because AnyNestedCondition 0 matched 2 did not; NestedCondition on MongoDataAutoConfiguration.AnyMongoClientAvailable.FallbackClientAvailable @ConditionalOnBean (types: com.mongodb.client.MongoClient; SearchStrategy: all) did not find any beans of type com.mongodb.client.MongoClient; NestedCondition on MongoDataAutoConfiguration.AnyMongoClientAvailable.PreferredClientAvailable @ConditionalOnBean (types: com.mongodb.MongoClient; SearchStrategy: all) did not find any beans of type com.mongodb.MongoClient
    - Bean method 'mappingMongoConverter' in 'MongoReactiveDataAutoConfiguration' not loaded because @ConditionalOnClass did not find required class 'com.mongodb.reactivestreams.client.MongoClient'

    ReplyDelete
  3. How to work on this if my mongo db is LDAP authenticated

    ReplyDelete