Configuring flapdoodle embedded mongo with Mongodb version 4 and replica - java

I am currently working on a spring boot application 2.0.3.RELEASE. I want to configure Flapdoodle MongoDb with MongoDb version 4.0 and I also want to set a single mongo instance and create replicas for it.
So far i haven't figured out the process of creating cluster and replicas using flapdoodle.
I am using
MongodConfigBuilder().version(Version.Main.DEVELOPMENT)
.replication(new Storage(null, null, 0))
.build();
i have read many questions here related to this configuration but none of them is related to my problem. eg
How to configure two instance mongodb use spring boot and spring data
The flapdoodle configuration has an implemetation for this but i am not sure how to access it.
https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/blob/master/src/main/java/de/flapdoodle/embed/mongo/tests/MongosSystemForTestFactory.java
Is there any way to configure it in my test class before application starts.
thanks

I had to start the Embedded mongo with replicaset in web Integration tests, used below code
#Configuration
public class MongoConfig {
public static int mongodPort;
public static String defaultHost = "localhost";
static {
try {
mongodPort = Network.getFreeServerPort();
} catch (IOException e) {
e.printStackTrace();
}
}
#Bean
public IMongodConfig prepareMongodConfig() throws IOException {
IMongoCmdOptions cmdOptions = new MongoCmdOptionsBuilder()
.useNoPrealloc(false)
.useSmallFiles(false)
.master(false)
.verbose(false)
.useNoJournal(false)
.syncDelay(0)
.build();
IMongodConfig mongoConfigConfig = new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(mongodPort, Network.localhostIsIPv6()))
.replication(new Storage(null, "testRepSet", 5000))
.configServer(false)
.cmdOptions(cmdOptions)
.build();
return mongoConfigConfig;
}
}
and before calling my controller I enabled the DB with replica set using below code
Public class ITtest {
public void setSystemProperty() {
System.setProperty("spring.data.mongodb.port", String.valueOf(MongoConfig.mongodPort));
System.setProperty("spring.data.mongodb.host", MongoConfig.defaultHost);
}
public static boolean isReplicaSetRun = false;
public static void setupMongoReplica() {
if (! isReplicaSetRun) {
System.out.println("Starting db on port: " +MongoConfig.mongodPort);
MongoClient client = new MongoClient(MongoConfig.defaultHost, MongoConfig.mongodPort);
client.getDatabase("admin").runCommand(new Document("replSetInitiate", new Document()));
client.close();
isReplicaSetRun = true;
}
}
#Test
#Order(1)
public void testParallel() {
setSystemProperty();
setupMongoReplica();
// call web controller
}
}
If want to run application, then enabling of replicaset can be done in implementation of ApplicationListener

Related

How to start H2 TCP server on Spring Boot application startup?

I'm able to start the H2 TCP server (database in a file) when running app as Spring Boot app by adding following line into the SpringBootServletInitializer main method:
#SpringBootApplication
public class NatiaApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
Server.createTcpServer().start();
SpringApplication.run(NatiaApplication.class, args);
}
}
But if I run the WAR file on Tomcat it doesn't work because the main method is not called. Is there a better universal way how to start the H2 TCP server on the application startup before beans get initialized? I use Flyway (autoconfig) and it fails on "Connection refused: connect" probably because the server is not running. Thank you.
This solution works for me. It starts the H2 server if the app runs as Spring Boot app and also if it runs on Tomcat. Creating H2 server as a bean did not work because the Flyway bean was created earlier and failed on "Connection refused".
#SpringBootApplication
#Log
public class NatiaApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
startH2Server();
SpringApplication.run(NatiaApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
startH2Server();
return application.sources(NatiaApplication.class);
}
private static void startH2Server() {
try {
Server h2Server = Server.createTcpServer().start();
if (h2Server.isRunning(true)) {
log.info("H2 server was started and is running.");
} else {
throw new RuntimeException("Could not start H2 server.");
}
} catch (SQLException e) {
throw new RuntimeException("Failed to start H2 server: ", e);
}
}
}
Yup, straight from the documentation, you can use a bean reference:
<bean id = "org.h2.tools.Server"
class="org.h2.tools.Server"
factory-method="createTcpServer"
init-method="start"
destroy-method="stop">
<constructor-arg value="-tcp,-tcpAllowOthers,-tcpPort,8043" />
There's also a servlet listener option that auto-starts/stops it.
That answers your question, but I think you should probably be using the embedded mode instead if it's deploying along with your Spring Boot application. This is MUCH faster and lighter on resources. You simply specify the correct URL and the database will start:
jdbc:h2:/usr/share/myDbFolder
(straight out of the cheat sheet).
There's a caveat that hasn't been considered in the other answers. What you need to be aware of is that starting a server is a transient dependency on your DataSource bean. This is due to the DataSource only needing a network connection, not a bean relationship.
The problem this causes is that spring-boot will not know about the h2 database needing to be fired up before creating the DataSource, so you could end up with a connection exception on application startup.
With the spring-framework this isn't a problem as you put the DB server startup in the root config with the database as a child. With spring boot AFAIK there's only a single context.
To get around this what you can do is create an Optional<Server> dependency on the data-source. The reason for Optional is you may not always start the server (configuration parameter) for which you may have a production DB.
#Bean(destroyMethod = "close")
public DataSource dataSource(Optional<Server> h2Server) throws PropertyVetoException {
HikariDataSource ds = new HikariDataSource();
ds.setDriverClassName(env.getProperty("db.driver"));
ds.setJdbcUrl(env.getProperty("db.url"));
ds.setUsername(env.getProperty("db.user"));
ds.setPassword(env.getProperty("db.pass"));
return ds;
}
For WAR packaging you can do this:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
Server.createTcpServer().start();
return new Class[] { NatiaApplication.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
You can do like this:
#Configuration
public class H2ServerConfiguration {
#Value("${db.port}")
private String h2TcpPort;
/**
* TCP connection to connect with SQL clients to the embedded h2 database.
*
* #see Server
* #throws SQLException if something went wrong during startup the server.
* #return h2 db Server
*/
#Bean
public Server server() throws SQLException {
return Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", h2TcpPort).start();
}
/**
* #return FlywayMigrationStrategy the strategy for migration.
*/
#Bean
#DependsOn("server")
public FlywayMigrationStrategy flywayMigrationStrategy() {
return Flyway::migrate;
}
}

spring bean startup/shutdown order configuration (start h2 db as server)

I'd like to create configuration/bean to automatically start H2DB in my development profile. I'd like to have it running as a tcp server. It's needed to be started before any DataSource configuration. Can someone tell me how to achieve this?
Wha have I done is
#Profile("h2")
#Component
public class H2DbServerConfiguration implements SmartLifecycle {
private static final Logger logger = LoggerFactory.getLogger(H2DbServerConfiguration.class);
private Server server;
#Override
public boolean isAutoStartup() {
return true;
}
#Override
public void stop(Runnable callback) {
stop();
new Thread(callback).start();
}
#Override
public void start() {
logger.debug("############################################");
logger.debug("############################################");
logger.debug("STARTING SERVER");
logger.debug("############################################");
logger.debug("############################################");
try {
server = Server.createTcpServer("-web", "-webAllowOthers", "-webPort", "8082").start();
} catch (SQLException e) {
throw new RuntimeException("Unable to start H2 server", e);
}
}
#Override
public void stop() {
logger.debug("############################################");
logger.debug("############################################");
logger.debug("STOPPING SERVER");
logger.debug("############################################");
logger.debug("############################################");
if (server != null)
if (server.isRunning(true))
server.stop();
}
#Override
public boolean isRunning() {
return server != null ? server.isRunning(true) : false;
}
#Override
public int getPhase() {
return 0;
}
}
but this isn't an option for me because component is created after datasource (I have liquibase setup so it's too late) and Phase is still the same that means FIFO order and I'd like to be FILO.
Mix #Profile and #Component seams to me a bad idea. Profiles are designed to work with Configuration (documentation)
Do you really need profile? In my opinion it makes sense if you have several possible configurations, one based on H2, and if you want be able to switch between these configurations (typically at start time by setting a properties...)
Manage the H2 server with a bean (documentation) seams correct to me (as suggested by Stefen). Maybe you will prefer annotations... If you want a spring profile, then you will need a Configuration object too. It will simply load the H2 server bean (in my opinion it's better to manage the H2 server lifecycle with a bean than with a context/config).
Create your server as a bean :
#Bean(initMethod = "start", destroyMethod = "stop")
Server h2Server() throws Exception {
return Server.createTcpServer("-tcp","-tcpAllowOthers","-tcpPort","9192");
}
Now you can configure spring to create other beans (e.g the datasource)
after the bean h2Server using #DependsOn
#DependsOn("h2Server")
#Bean
DataSource dataSource(){
...
}
Hi, what about using spring boot? It has automatically configured datasource so I don't want to reconfigure it.
You are right, to use the above approach you have to create your own datasource in order to annotate it with #DependsOn .
But it looks like this is not really necessary.
In one of my projects I am creating the h2Server as a bean as described.
I use the datasource created by spring, so without any #DependsOn.
It works perfectly. Just give it a try.
Your solution with SmartLifecycle does not work, because it creates the server on ApplicationContext refresh, which happens after all beans (including the datasource ) were created.

Override default Spring-Boot application.properties settings in Junit Test with dynamic value

I want to override properties defined in application.properties in tests, but #TestPropertySource only allows to provide predefined values.
What I need is to start a server on a random port N, then pass this port to spring-boot application. The port has to be ephemeral to allow running multiple tests on the same host at the same time.
I don't mean the embedded http server (jetty), but some different server that is started at the beginning of the test (e.g. zookeeper) and the application being tested has to connect to it.
What's the best way to achieve this?
(here's a similar question, but answers do not mention a solution for ephemeral ports - Override default Spring-Boot application.properties settings in Junit Test)
As of Spring Framework 5.2.5 and Spring Boot 2.2.6 you can use Dynamic Properties in tests:
#DynamicPropertySource
static void dynamicProperties(DynamicPropertyRegistry registry) {
registry.add("property.name", "value");
}
Thanks to the changes made in Spring Framework 5.2.5, the use of #ContextConfiguration and the ApplicationContextInitializer can be replaced with a static #DynamicPropertySource method that serves the same purpose.
#SpringBootTest
#Testcontainers
class SomeSprintTest {
#Container
static LocalStackContainer localStack =
new LocalStackContainer().withServices(LocalStackContainer.Service.S3);
#DynamicPropertySource
static void initialize(DynamicPropertyRegistry registry) {
AwsClientBuilder.EndpointConfiguration endpointConfiguration =
localStack.getEndpointConfiguration(LocalStackContainer.Service.S3);
registry.add("cloud.aws.s3.default-endpoint", endpointConfiguration::getServiceEndpoint);
}
}
You could override the value of the port property in the #BeforeClass like this:
#BeforeClass
public static void beforeClass() {
System.setProperty("zookeeper.port", getRandomPort());
}
The "clean" solution is to use an ApplicationContextInitializer.
See this answer to a similar question.
See also this github issue asking a similar question.
To summarize the above mentioned posts using a real-world example that's been sanitized to protect copyright holders (I have a REST endpoint which uses an #Autowired DataSource which needs to use the dynamic properties to know which port the in-memory MySQL database is using):
Your test must declare the initializer (see the #ContextConfiguration line below).
// standard spring-boot test stuff
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("local")
#ContextConfiguration(
classes = Application.class,
// declare the initializer to use
initializers = SpringTestDatabaseInitializer.class)
// use random management port as well so we don't conflict with other running tests
#TestPropertySource(properties = {"management.port=0"})
public class SomeSprintTest {
#LocalServerPort
private int randomLocalPort;
#Value("${local.management.port}")
private int randomManagementPort;
#Test
public void testThatDoesSomethingUseful() {
// now ping your service that talks to the dynamic resource
}
}
Your initializer needs to add the dynamic properties to your environment. Don't forget to add a shutdown hook for any cleanup that needs to run. Following is an example that sets up an in-memory database using a custom DatabaseObject class.
public class SpringTestDatabaseInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final int INITIAL_PORT = 0; // bind to an ephemeral port
private static final String DB_USERNAME = "username";
private static final String DB_PASSWORD = "password-to-use";
private static final String DB_SCHEMA_NAME = "default-schema";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
DatabaseObject databaseObject = new InMemoryDatabaseObject(INITIAL_PORT, DB_USERNAME, DB_PASSWORD, DB_SCHEMA_NAME);
registerShutdownHook(databaseObject);
int databasePort = startDatabase(databaseObject);
addDatabasePropertiesToEnvironment(applicationContext, databasePort);
}
private static void addDatabasePropertiesToEnvironment(ConfigurableApplicationContext applicationContext, int databasePort) {
String url = String.format("jdbc:mysql://localhost:%s/%s", databasePort, DB_SCHEMA_NAME);
System.out.println("Adding db props to environment for url: " + url);
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"db.port=" + databasePort,
"db.schema=" + DB_SCHEMA_NAME,
"db.url=" + url,
"db.username=" + DB_USERNAME,
"db.password=" + DB_PASSWORD);
}
private static int startDatabase(DatabaseObject database) {
try {
database.start();
return database.getBoundPort();
} catch (Exception e) {
throw new IllegalStateException("Failed to start database", e);
}
}
private static void registerShutdownHook(DatabaseObject databaseObject) {
Runnable shutdownTask = () -> {
try {
int boundPort = databaseObject.getBoundPort();
System.out.println("Shutting down database at port: " + boundPort);
databaseObject.stop();
} catch (Exception e) {
// nothing to do here
}
};
Thread shutdownThread = new Thread(shutdownTask, "Database Shutdown Thread");
Runtime.getRuntime().addShutdownHook(shutdownThread);
}
}
When I look at the logs, it shows that for both of my tests that use this initializer class, they use the same object (the initialize method only gets called once, as does the shutdown hook). So it starts up a database, and leaves it running until both tests finish, then shuts the database down.

Updating Dropwizard config at runtime

Is it possible to have my app update the config settings at runtime? I can easily expose the settings I want in my UI but is there a way to allow the user to update settings and make them permanent ie save them to the config.yaml file? The only way I can see it to update the file by hand then restart the server which seems a bit limiting.
Yes. It is possible to reload the service classes at runtime.
Dropwizard by itself does not have the way to reload the app, but jersey has.
Jersey uses a container object internally to maintain the running application. Dropwizard uses the ServletContainer class of Jersey to run the application.
How to reload the app without restarting it -
Get a handle to the container used internally by jersey
You can do this by registering a AbstractContainerLifeCycleListener in Dropwizard Environment before starting the app. and implement its onStartup method as below -
In your main method where you start the app -
//getting the container instance
environment.jersey().register(new AbstractContainerLifecycleListener() {
#Override
public void onStartup(Container container) {
//initializing container - which will be used to reload the app
_container = container;
}
});
Add a method to your app to reload the app. It will take in the list of string which are the names of the service classes you want to reload. This method will call the reload method of the container with the new custom DropWizardConfiguration instance.
In your Application class
public static synchronized void reloadApp(List<String> reloadClasses) {
DropwizardResourceConfig dropwizardResourceConfig = new DropwizardResourceConfig();
for (String className : reloadClasses) {
try {
Class<?> serviceClass = Class.forName(className);
dropwizardResourceConfig.registerClasses(serviceClass);
System.out.printf(" + loaded class %s.\n", className);
} catch (ClassNotFoundException ex) {
System.out.printf(" ! class %s not found.\n", className);
}
}
_container.reload(dropwizardResourceConfig);
}
For more details see the example documentation of jersey - jersey example for reload
Consider going through the code and documentation of following files in Dropwizard/Jersey for a better understanding -
Container.java
ContainerLifeCycleListener.java
ServletContainer.java
AbstractContainerLifeCycleListener.java
DropWizardResourceConfig.java
ResourceConfig.java
No.
Yaml file is parsed at startup and given to the application as Configuration object once and for all. I believe you can change the file after that but it wouldn't affect your application until you restart it.
Possible follow up question: Can one restart the service programmatically?
AFAIK, no. I've researched and read the code somewhat for that but couldn't find a way to do that yet. If there is, I'd love to hear that :).
I made a task that reloads the main yaml file (it would be useful if something in the file changes). However, it is not reloading the environment. After researching this, Dropwizard uses a lot of final variables and it's quite hard to reload these on the go, without restarting the app.
class ReloadYAMLTask extends Task {
private String yamlFileName;
ReloadYAMLTask(String yamlFileName) {
super("reloadYaml");
this.yamlFileName = yamlFileName;
}
#Override
public void execute(ImmutableMultimap<String, String> parameters, PrintWriter output) throws Exception {
if (yamlFileName != null) {
ConfigurationFactoryFactory configurationFactoryFactory = new DefaultConfigurationFactoryFactory<ReportingServiceConfiguration>();
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = Jackson.newObjectMapper();
final ConfigurationFactory<ServiceConfiguration> configurationFactory = configurationFactoryFactory.create(ServiceConfiguration.class, validator, objectMapper, "dw");
File confFile = new File(yamlFileName);
configurationFactory.build(new File(confFile.toURI()));
}
}
}
You can change the configuration in the YAML and read it while your application is running. This will not however restart the server or change any server configurations. You will be able to read any changed custom configurations and use them. For example, you can change the logging level at runtime or reload other custom settings.
My solution -
Define a custom server command. You should use this command to start your application instead of the "server" command.
ArgsServerCommand.java
public class ArgsServerCommand<WC extends WebConfiguration> extends EnvironmentCommand<WC> {
private static final Logger LOGGER = LoggerFactory.getLogger(ArgsServerCommand.class);
private final Class<WC> configurationClass;
private Namespace _namespace;
public static String COMMAND_NAME = "args-server";
public ArgsServerCommand(Application<WC> application) {
super(application, "args-server", "Runs the Dropwizard application as an HTTP server specific to my settings");
this.configurationClass = application.getConfigurationClass();
}
/*
* Since we don't subclass ServerCommand, we need a concrete reference to the configuration
* class.
*/
#Override
protected Class<WC> getConfigurationClass() {
return configurationClass;
}
public Namespace getNamespace() {
return _namespace;
}
#Override
protected void run(Environment environment, Namespace namespace, WC configuration) throws Exception {
_namespace = namespace;
final Server server = configuration.getServerFactory().build(environment);
try {
server.addLifeCycleListener(new LifeCycleListener());
cleanupAsynchronously();
server.start();
} catch (Exception e) {
LOGGER.error("Unable to start server, shutting down", e);
server.stop();
cleanup();
throw e;
}
}
private class LifeCycleListener extends AbstractLifeCycle.AbstractLifeCycleListener {
#Override
public void lifeCycleStopped(LifeCycle event) {
cleanup();
}
}
}
Method to reload in your Application -
_ymlFilePath = null; //class variable
public static boolean reloadConfiguration() throws IOException, ConfigurationException {
boolean reloaded = false;
if (_ymlFilePath == null) {
List<Command> commands = _configurationBootstrap.getCommands();
for (Command command : commands) {
String commandName = command.getName();
if (commandName.equals(ArgsServerCommand.COMMAND_NAME)) {
Namespace namespace = ((ArgsServerCommand) command).getNamespace();
if (namespace != null) {
_ymlFilePath = namespace.getString("file");
}
}
}
}
ConfigurationFactoryFactory configurationFactoryFactory = _configurationBootstrap.getConfigurationFactoryFactory();
ValidatorFactory validatorFactory = _configurationBootstrap.getValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = _configurationBootstrap.getObjectMapper();
ConfigurationSourceProvider provider = _configurationBootstrap.getConfigurationSourceProvider();
final ConfigurationFactory<CustomWebConfiguration> configurationFactory = configurationFactoryFactory.create(CustomWebConfiguration.class, validator, objectMapper, "dw");
if (_ymlFilePath != null) {
// Refresh logging level.
CustomWebConfiguration webConfiguration = configurationFactory.build(provider, _ymlFilePath);
LoggingFactory loggingFactory = webConfiguration.getLoggingFactory();
loggingFactory.configure(_configurationBootstrap.getMetricRegistry(), _configurationBootstrap.getApplication().getName());
// Get my defined custom settings
CustomSettings customSettings = webConfiguration.getCustomSettings();
reloaded = true;
}
return reloaded;
}
Although this feature isn't supported out of the box by dropwizard, you're able to accomplish this fairly easy with the tools they give you.
Before I get started, note that this isn't a complete solution for the question asked as it doesn't persist the updated config values to the config.yml. However, this would be easy enough to implement yourself simply by writing to the config file from the application. If anyone would like to write this implementation feel free to open a PR on the example project I've linked below.
Code
Start off with a minimal config:
config.yml
myConfigValue: "hello"
And it's corresponding configuration file:
ExampleConfiguration.java
public class ExampleConfiguration extends Configuration {
private String myConfigValue;
public String getMyConfigValue() {
return myConfigValue;
}
public void setMyConfigValue(String value) {
myConfigValue = value;
}
}
Then create a task which updates the config:
UpdateConfigTask.java
public class UpdateConfigTask extends Task {
ExampleConfiguration config;
public UpdateConfigTask(ExampleConfiguration config) {
super("updateconfig");
this.config = config;
}
#Override
public void execute(Map<String, List<String>> parameters, PrintWriter output) {
config.setMyConfigValue("goodbye");
}
}
Also for demonstration purposes, create a resource which allows you to get the config value:
ConfigResource.java
#Path("/config")
public class ConfigResource {
private final ExampleConfiguration config;
public ConfigResource(ExampleConfiguration config) {
this.config = config;
}
#GET
public Response handleGet() {
return Response.ok().entity(config.getMyConfigValue()).build();
}
}
Finally wire everything up in your application:
ExampleApplication.java (exerpt)
environment.jersey().register(new ConfigResource(configuration));
environment.admin().addTask(new UpdateConfigTask(configuration));
Usage
Start up the application then run:
$ curl 'http://localhost:8080/config'
hello
$ curl -X POST 'http://localhost:8081/tasks/updateconfig'
$ curl 'http://localhost:8080/config'
goodbye
How it works
This works simply by passing the same reference to the constructor of ConfigResource.java and UpdateConfigTask.java. If you aren't familiar with the concept see here:
Is Java "pass-by-reference" or "pass-by-value"?
The linked classes above are to a project I've created which demonstrates this as a complete solution. Here's a link to the project:
scottg489/dropwizard-runtime-config-example
Footnote: I haven't verified this works with the built in configuration. However, the dropwizard Configuration class which you need to extend for your own configuration does have various "setters" for internal configuration, but it may not be safe to update those outside of run().
Disclaimer: The project I've linked here was created by me.

How do I change my java code to use replset? with Spring Data and MongoDB

I would like to know how to change my java code to support replset with spring-data and MongoDB.
I have 3 MongoDB servers running.. example:
./mongod --dbpath=/home/jsmith/tmp/db1 --replSet=spring --port=27017
./mongod --dbpath=/home/jsmith/tmp/db2 --replSet=spring --port=27027
./mongod --dbpath=/home/jsmith/tmp/db3 --replSet=spring --port=27037
if I do rs.status() I can see that if the db on 27017 goes down then one of the others become primary so I know that mongoDB is working right but in my java code if I try to run it I get the following error:
Exception in thread "main" org.springframework.dao.DataAccessResourceFailureException: can't call something : /127.0.0.1:27017/demo
Its looking only on port 27017
here is my mongodbconfig:
#Configuration
#EnableMongoRepositories
#ComponentScan(basePackageClasses = {MongoDBApp.class})
#PropertySource("classpath:application.properties")
public class MongoConfiguration extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "demo";
}
#Override
public Mongo mongo() throws Exception {
return new Mongo(new ArrayList<ServerAddress>() {{ add(new ServerAddress("127.0.0.1", 27017)); add(new ServerAddress("127.0.0.1", 27027)); add(new ServerAddress("127.0.0.1", 27037)); }});
}
#Override
protected String getMappingBasePackage() {
return "com.xxxx.mongodb.example.domain";
}
}
how do I change it to support replset? but if its reading and one of the servers goes down I get a error.. anyway to make in reconnect?
The URI method should work, or there's a clearer way to initialise the replica set using a list of servers:
final List<ServerAddress> seeds = Arrays.asList(new ServerAddress("127.0.0.1", 27017),
new ServerAddress("127.0.0.1", 27027),
new ServerAddress("127.0.0.1", 27037));
final Mongo mongo = new Mongo(seeds);
This is how I do it:
String mongoURI="mongodb://myUsrName:pass#mongoServer-001.company.com:27017,mongoServer-002.company.com:27017,mongoServer-003.company.com:27017/myDBname?waitqueuemultiple=1500&w=1&maxpoolsize=40&safe=true";
MongoURI uri = new MongoURI(mongoURI);
Mongo mongo = new Mongo(uri);
I specify the 3 servers in the URI (along with extra parameters like max pool size).
The third server (mongoServer-003) is the arbiter and it doesn't store any info. The arbiter helps in the election of the primary server when the current primary goes down. Take a look at this article.
With this configuration, the app can keep working even if the primary server goes down.
You can also do it the CustomEditorConfigurer and a class that implements PropertyEditorRegistrar.
So you your configuration class needs this:
#Bean
public static CustomEditorConfigurer customEditorConfigurer(){
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setPropertyEditorRegistrars(
new PropertyEditorRegistrar[]{new ServerAddressPropertyEditorRegistrar()});
return configurer;
}
#Override
protected String getDatabaseName() {
return authenticationDb;
}
#Override
#Bean
public MongoClient mongoClient() {
MongoClient mongoClient = new MongoClient(Arrays.asList(new ServerAddress(host, port)), mongoCredentials(), mongoClientOptions());
return mongoClient;
}
public final class ServerAddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
#Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(ServerAddress[].class, new ServerAddressPropertyEditor());
}
}

Categories

Resources