Configuring the Batch Configurer to include transaction with Spring Data MongoDb in Spring Batch - mongodb

In our application, we implemented Spring Data MongoDB transactions by following this guide
https://www.baeldung.com/spring-data-mongodb-transactions
However, we keep facing this issue.
I am unsure on how I should override the BatchConfigurer.
I have tried to follow this guide, but it uses an earlier version of Spring Data MongoDb. Some of the classes are deprecated
https://dzone.com/articles/spring-batch-goodies-with-mongodb
Error:
Description:
The bean 'transactionManager', defined in class path resource [org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/pragnamic/common/infrastructure/MongoDbConfig.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
MongoDbConfig
#Configuration
public class MongoDbConfig extends AbstractMongoClientConfiguration {
private final List<Converter<?,?>> converters = new ArrayList<>();
#Value("${spring.data.mongodb.uri}")
private String uri;
#Value("${spring.data.mongodb.database}")
private String database;
#Bean
MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) {
return new MongoTransactionManager(mongoDatabaseFactory);
}
#Override
protected String getDatabaseName() {
return database;
}
#Override
public MongoClient mongoClient() {
ConnectionString connectionString = new ConnectionString(uri);
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Bean
public MongoCustomConversions customConversions() {
converters.add(new DomainObjectIdWriterConverter());
return new MongoCustomConversions(converters);
}
}
We are also using the latest version of Spring Data MongoDB
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Related

Make MongoDB Spring Boot test use existing configuration and embedded test database

this is my existing MongoDB configuration:
#Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {
#Autowired
MongoDBProperties mongoDBProperties;
#Override
public MongoClient mongoClient() {
ConnectionString connectionString = new ConnectionString(mongoDBProperties.getUrl());
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Override
public MongoCustomConversions customConversions() {
return new MongoCustomConversions(Arrays.asList(new OffsetDateTimeReadConverter(), new OffsetDateTimeWriteConverter()));
}
#Override
protected String getDatabaseName() {
return mongoDBProperties.getDatabase();
}
}
I need the converters because the document that I store contains OffsetDataTime fields.
I want to test saving a document. I have included this dependency:
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
My test looks like this:
#DataMongoTest
public class VersionRepositoryTestCase {
#Autowired
MyDocumentRepository cut;
#Autowired
MongoDBProperties mongoDBProperties;
#Test
public void speichernVonVersionMitVersionInfoFunktioniert() {
MyDocument myDocument = createMyDocument();
cut.save(versionDocument);
}
}
My problem is: if I use #SpringBootTest, it is not the embedded MongoDBD that is being used. if I use the #DataMongoTest and override the MongoDbProperties bean, the converters are not used so the test fails. So how can I use my config class with the embedded test mongo db instance?
It works with #Import anotation above the Test class.

Springboot MongoDB connection URI is specifed in other key (not using spring.data.mongodb.uri)

For my Springboot application, I have a requirement that MongoDB URI should be specified with "app1.mongodb.uri" in application.properties. Yes we don't want to use "spring.data.mongodb.uri" because I was told that it's misleading (what!?). Does anyone know what is the simplest way to do that ? My application is all running fine, and I'm so reluctant to make any big change because of this "requirement".
Figured it out how to do it. The trick is to override the beam MongoClient and Mongotemplate.
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
#SuppressWarnings("deprecation")
#Configuration
#EnableMongoRepositories
#PropertySource("classpath:application.properties")
public class MongoDBConfig extends AbstractMongoConfiguration {
#Value("${app1.mongodb.db}")
private String database;
#Value("${app1.mongodb.uri}")
private String uri;
#Override
#Bean
public MongoClient mongoClient() {
MongoClientURI mongoURI = new MongoClientURI(uri);
MongoClient client = new MongoClient(mongoURI);
return client;
}
#Override
protected String getDatabaseName() {
return database;
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoClient(), getDatabaseName());
}
}

MongoTemplate Custom Config using Spring boot

I was looking to change WriteResultChecking property of mongoTemplate whilst working on Spring boot app (2.0.5). I found out a way via extending AbstractMongoConfiguration as below.
I got the same working, however i found this approach a bit risky.
Saying this because this approach forced me to write a implementation for
public MongoClient mongoClient() {
return new MongoClient(host, port);
}
Now MongoClient is the central class to maintain connections with MongoDB and if i am forced to write implementation for the same, then i may be possibly missing out on optimizations that spring framework does.
Can someone please suggest any other optimal way of overriding some properties/behaviours without having to tinker too much ?
#Configuration
public class MyMongoConfigs extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.database}")
private String databaseName;
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private int port;
#Override
public MongoClient mongoClient() {
return new MongoClient(host, port);
}
#Override
protected String getDatabaseName() {
return databaseName;
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
MongoTemplate myTemp = new MongoTemplate(mongoDbFactory(), mappingMongoConverter());
**myTemp.setWriteResultChecking(WriteResultChecking.EXCEPTION);**
return myTemp;
}
You are in right direction. Using AbstractMongoConfiguration you override the configs that you need to customize and it's the right way to do it. AbstractMongoConfiguration is still Spring Provided class, so the you don't have to worry about optimization, unless you mess with your configuration.
This is my approach:
package app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.WriteResultChecking;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import com.mongodb.WriteConcern;
#Configuration
class ApplicationConfig {
#Bean
MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
mongoTemplate.setWriteConcern(WriteConcern.MAJORITY);
mongoTemplate.setWriteResultChecking(WriteResultChecking.EXCEPTION);
return mongoTemplate;
}
}
I have figured this out by inspecting the source code of org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.
I wrote CustomMongoTemplate which override method find() with added criteria to check if document has 'deletedAt' field;
Custom MongoTemplate:
public class CustomMongoTemplate extends MongoTemplate {
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
super(mongoDbFactory, mongoConverter);
}
#Override
public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.find(query, entityClass, collectionName);
}
Then create Bean in configuration class:
#Configuration
public class MyConfiguration {
//...
#Bean(name = "mongoTemplate")
CustomMongoTemplate customMongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
return new CustomMongoTemplate(databaseFactory, converter);
}
//...
}
And last thing - allow Spring to override default MongoTemplate bean. Add next thing to your application.properties file:
spring.main.allow-bean-definition-overriding=true

Using embedded MongoDB in Spring JUnit #WebMvcTest

I'm currently using MongoDB in my Spring application. Since I added Mongo my endpoint tests no longer work due to the following error:
No qualifying bean of type 'xxx' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
The repository which I Autowire in a controller is as follows:
private final RuleRepository ruleRepository;
#Autowired
public TestController(RuleRepository ruleRepository) {
this.ruleRepository = ruleRepository;
}
I assume that this has to do with Mongo and the fact that I currently use AutoConfiguration for it. For the test I added the Flapdoodle Embed Mongo dependency, which seems to be used for testing purposes in a lot of examples, to my pom.xml with the scope set to test:
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>2.0.3</version>
<scope>test</scope>
</dependency>
My test class looks like this:
RunWith(SpringRunner.class)
#WebMvcTest(value = RouteController.class, secure = false)
#ActiveProfiles("test")
public class TestControllerEndpointTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private RuleRepository ruleRepository;
#Before
public void setupTests() {
//Setup for the tests
}
//Actual tests
}
I also created a Configuration class for the Mongo Test Database, but I don't know how to correctly register it:
#Configuration
#Profile("test")
public class TestMongoConfig {
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Bean(destroyMethod = "close")
public Mongo mongo(MongodProcess mongodProcess) throws IOException {
Net net = mongodProcess.getConfig().net();
return new MongoClient(net.getServerAddress().getHostName(), net.getPort());
}
#Bean(destroyMethod = "stop")
public MongodProcess mongodProcess(MongodExecutable mongodExecutable) throws IOException {
return mongodExecutable.start();
}
#Bean(destroyMethod = "stop")
public MongodExecutable mongodExecutable(MongodStarter mongodStarter, IMongodConfig iMongodConfig) throws IOException {
return mongodStarter.prepare(iMongodConfig);
}
#Bean
public IMongodConfig mongodConfig() throws IOException {
return new MongodConfigBuilder().version(Version.Main.PRODUCTION).build();
}
#Bean
public MongodStarter mongodStarter() {
return MongodStarter.getDefaultInstance();
}
}
How do I get an endpoint test that is annotated with #WebMvcTest to use the embedded Mongo database?
After banging our heads for a while we found the #AutoConfigureDataMongo annotation.
import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo;
#RunWith(SpringRunner.class)
#WebMvcTest(value = SampleController.class, secure = false)
#AutoConfigureDataMongo
public class SampleControllerTest {
Just annotate your controller with it and you should see org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongo logs when running this controller tests.

#EnableMongoAuditing for MongoDB on Cloud Foundry / mongolab

My setup works on my local but not when I deploy it to CloudFoundry/mongolab.
The config is very similar to the docs.
My local spring config
#Configuration
#Profile("dev")
#EnableMongoAuditing
#EnableMongoRepositories(basePackages = "com.foo.model")
public class SpringMongoConfiguration extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
return "myDb";
}
#Override
public Mongo mongo() throws Exception {
return new MongoClient("localhost");
}
#Bean
public AuditorAware<User> myAuditorProvider() {
return new SpringSecurityAuditorAware();
}
}
This is the cloud foundry setup
#Configuration
#Profile("cloud")
#EnableMongoAuditing
#EnableMongoRepositories(basePackages = "com.foo.model")
public class SpringCloudMongoDBConfiguration extends AbstractMongoConfiguration {
private Cloud getCloud() {
CloudFactory cloudFactory = new CloudFactory();
return cloudFactory.getCloud();
}
#Bean
public MongoDbFactory mongoDbFactory() {
Cloud cloud = getCloud();
MongoServiceInfo serviceInfo = (MongoServiceInfo) cloud.getServiceInfo(cloud.getCloudProperties().getProperty("cloud.services.mongo.id"));
String serviceID = serviceInfo.getId();
return cloud.getServiceConnector(serviceID, MongoDbFactory.class, null);
}
#Override
protected String getDatabaseName() {
Cloud cloud = getCloud();
return cloud.getCloudProperties().getProperty("cloud.services.mongo.id");
}
#Override
public Mongo mongo() throws Exception {
Cloud cloud = getCloud();
return new MongoClient(cloud.getCloudProperties().getProperty("cloud.services.mongo.connection.host"));
}
#Bean
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoDbFactory());
}
#Bean
public AuditorAware<User> myAuditorProvider() {
return new SpringSecurityAuditorAware();
}
}
And the error I'm getting when I try to save a document in Cloud Foundry is:
OUT ERROR: org.springframework.data.support.IsNewStrategyFactorySupport - Unexpected error
OUT java.lang.IllegalArgumentException: Unsupported entity com.foo.model.project.Project! Could not determine IsNewStrategy.
OUT at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:739)
OUT at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
OUT at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
Any ideas? Is it my config file etc..?
Thanks in advance
Niclas
This is usually caused if the Mongo mapping metadata obtained for entities does not scan entities at application startup. By default, AbstractMongoConfiguration uses the package of the actual configuration class to look for #Document annotated classes at startup.
The exception message makes me assume that SpringCloudMongoDBConfiguration is not located in any of the super packages of com.foo.model.project. There are two solutions to this:
Stick to the convenience of putting application configuration classes into the root package of your application. This will cause your application packages be scanned for domain classes, metadata obtained, and the is-new-detection work as expected.
Manually hand the package containing domain classes to the infrastructure by overriding MongoConfiguration.getMappingBasePackage().
The reason you might see the configuration working in the local environment is that the mapping metadata might be obtained through a non-persisting persistence operation (e.g. a query) and everything else proceeding from there.