Spring Boot & Mongo: custom MappingMongoConverter doesn't work during DotReplacement - spring

I need to achive dotReplacementKey
I'm using MongoDB with such config:
#Configuration
public class MongoTemplateConfig {
#Value("${adserver_mongo_connection_string}")
private String databaseConnectionString;
#Bean
public MongoDbFactory mongoDbFactory() throws UnknownHostException {
MongoClientURI uri = new MongoClientURI(databaseConnectionString.trim());
return new SimpleMongoDbFactory(uri);
}
#Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
return new MongoTemplate(mongoDbFactory());
}
#Bean
public MappingMongoConverter mongoConverter(MongoDbFactory mongoFactory, MongoMappingContext mongoMappingContext) throws Exception {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoFactory);
MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
mongoConverter.setMapKeyDotReplacement(".");
return mongoConverter;
}
}
I'm doing upsert as follows:
mongoTemplate.bulkOps(...).upsert(...)
but during runtime via debug I found out that different MappingMongoConverter is used rather then this that was configured as a #Bean
btw, if #Inject wherever MappingMongoConverter I get proper bean from config with keyDotReplacement="."
but looks like Spring Boot uses another one under the hood
P.S. I have seen this question without correct answer, but tried with mongoConverter.afterPropertiesSet() and obviously it doesn't work as well

It's a bit ridiculous but the point is that we should pass this custom mappingMongoConverter into MongoTemplate initialization :
#Bean
public MongoTemplate mongoTemplate(MappingMongoConverter mappingMongoConverter) throws UnknownHostException {
return new MongoTemplate(mongoDbFactory(), mappingMongoConverter);
}

Related

How to implement spring data mongo multidatabase transactions

I created an application that copies data from one database to another one based on this tutorial.
In fact, I need to make a method that inserts in two different databases transactional.
Is this possible with MongoDB ? and how can I implement it?
To use multi-document transactions in MongoDB across multiple databases in Spring Data MongoDB, you need to configure a MongoTemplate per database, but all of them must use the same MongoDbFactory because it is treated as the transactional resource.
This means that you will need to override a couple of methods of MongoTemplate to make it use the database it should use (and not the one configured inside SimpleMongoClientDbFactory).
Let's assume that your databases are called 'one' and 'two'. Then it goes like this:
public class MongoTemplateWithFixedDatabase extends MongoTemplate {
private final MongoDbFactory mongoDbFactory;
private final String databaseName;
public MongoTemplateWithFixedDatabase(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter, String databaseName) {
super(mongoDbFactory, mappingMongoConverter);
this.mongoDbFactory = mongoDbFactory;
this.databaseName = databaseName;
}
#Override
protected MongoDatabase doGetDatabase() {
return MongoDatabaseUtils.getDatabase(databaseName, mongoDbFactory, ON_ACTUAL_TRANSACTION);
}
}
and
#Bean
public MongoDbFactory mongoDbFactory() {
// here, some 'default' database name is configured, the following MongoTemplate instances will ignore it
return new SimpleMongoDbFactory(mongoClient(), getDatabaseName());
}
#Bean
public MongoTransactionManager mongoTransactionManager() {
return new MongoTransactionManager(mongoDbFactory());
}
#Bean
public MongoTemplate mongoTemplateOne(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter) {
MongoTemplate template = new MongoTemplateWithFixedDatabase(mongoDbFactory,
mappingMongoConverter, "one");
return template;
}
#Bean
public MongoTemplate mongoTemplateTwo(MongoDbFactory mongoDbFactory,
MappingMongoConverter mappingMongoConverter) {
MongoTemplate template = new MongoTemplateWithFixedDatabase(mongoDbFactory,
mappingMongoConverter, "two");
return template;
}
Then just inject mongoTemplateOne and mongoTemplateTwo in your service, mark its method with #Transactional and it should work.
Reactive case
In the reactive case, it's very similar. Of course, you need to use reactive versions of the classes like ReactiveMongoTemplate, ReactiveMongoDatabaseFactory, ReactiveMongoTransactionManager.
There is also a couple of caveats. First, you have to override 3 methods, not 2 (as getCollection(String) also needs to be overridden). Also, I had to do it with an abstract class to make it work:
#Bean
public ReactiveMongoOperations reactiveMongoTemplateOne(
#ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory,
MappingMongoConverter mappingMongoConverter) {
ReactiveMongoTemplate template = new ReactiveMongoTemplate(reactiveMongoDatabaseFactory,
mappingMongoConverter) {
#Override
protected Mono<MongoDatabase> doGetDatabase() {
return ReactiveMongoDatabaseUtils.getDatabase("one", reactiveMongoDatabaseFactory,
ON_ACTUAL_TRANSACTION);
}
#Override
public MongoDatabase getMongoDatabase() {
return reactiveMongoDatabaseFactory.getMongoDatabase(getDatabaseName());
}
#Override
public MongoCollection<Document> getCollection(String collectionName) {
Assert.notNull(collectionName, "Collection name must not be null!");
try {
return reactiveMongoDatabaseFactory.getMongoDatabase(getDatabaseName())
.getCollection(collectionName);
} catch (RuntimeException e) {
throw potentiallyConvertRuntimeException(e,
reactiveMongoDatabaseFactory.getExceptionTranslator());
}
}
private RuntimeException potentiallyConvertRuntimeException(RuntimeException ex,
PersistenceExceptionTranslator exceptionTranslator) {
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
return resolved == null ? ex : resolved;
}
};
return template;
}
P.S. the provided code was tested with spring-data-mongodb 2.2.4.

get MongoDatabase with spring data mongodb

I am trying to tail the mongo oplog collection indefinitely. The code that I currently use is as below.
MongoClient mongoClient = new MongoClient(<host>,<27017>);
MongoCollection oplogColl =
mongoClient
.getDatabase("local")
.getCollection("oplog.rs");
MongoCursor oplogCursor =
oplogColl
.find(new Document("ts", filter))
.cursorType(CursorType.TailableAwait)
.noCursorTimeout(true)
.sort(new Document("$natural", 1))
.iterator();
I am trying to get the same implementation using spring-data where the mongo uri will be specified in the properties file. Hence I need to access MongoDatabase or MongoClient.
Tried using MongodbFactory class in spring, but it returns a instance of type DB, which is a old mongo implementation to access mongo.
How can I get to use MongoDatabase/MongoCollection/MongoClient using spring data.
MongoClient extends Mongo, so
#Configuration
public class AppConfig {
public #Bean Mongo mongo() throws UnknownHostException {
return new Mongo("localhost");
}
}
If you don't want to code to Mongo classes directly
/*
* Factory bean that creates the com.mongodb.Mongo instance
*/
#Bean
public MongoClientFactoryBean mongo() {
MongoClientFactoryBean mongo = new MongoClientFactoryBean();
mongo.setHost("localhost");
return mongo;
}
Edit:
#Configuration
public class AppConfig {
public #Bean MongoClient mongo() throws UnknownHostException {
return new MongoClient("localhost");
}
}
Usage:
#Autowired
private Mongo mongo;
MongoOperations mongoOps = new MongoTemplate(mongo, "databaseName");
Edit:
#Autowired
private MongoClient mongoClient;
MongoOperations mongoOps = new MongoTemplate(mongoClient, "databaseName");

Spring Boot 1.5.4 Filter out null values in json response

I am using Spring Boot 1.5.4 version . I am using spring-ws getWebServiceTemplate() to make a webservice call. The SOAP response has lot of null values for the fields.
I am trying to filter out the null values in the JSON response. None of the following approaches seem to work:
Setting the property in the application.properties:
spring.jackson.default-property-inclusion:NON_NULL`
Setting it in Configuration class using Jackson2ObjectMapperBuilder:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
return builder;
}
Please advise.
lva
I am using Spring Boot 1.5.6.RELEASE version, and you can reference
customize-the-jackson-objectmapper
Following code is work:
#SpringBootApplication
public class Application {
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
return builder;
}
public static void main(String[] args) {
SpringApplication.run(DbeeApiApplication.class, args);
}
}
Or you can filter from MappingJackson2HttpMessageConverter, for example:
#Configuration
class WebMvcConfiguration extends WebMvcConfigurationSupport {
#Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for(HttpMessageConverter<?> converter: converters) {
if(converter instanceof MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter)converter).getObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
}
}
}
Using the following in application.properties worked.
spring.jackson.default-property-inclusion=NON_NULL

How to use user defined database proxy in #DataJpaTest

We need to track database metrics so we are using datasource-proxy to track this to integrate the same in spring boot project we have created custom datasource
as below
#Component
#Slf4j
#ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceBeanConfig
{
public DataSource actualDataSource()
{
EmbeddedDatabaseBuilder databaseBuilder = new EmbeddedDatabaseBuilder();
return databaseBuilder.setType(EmbeddedDatabaseType.H2).build();
}
#Bean
#Primary
public DataSource dataSource() {
// use pretty formatted query with multiline enabled
PrettyQueryEntryCreator creator = new PrettyQueryEntryCreator();
creator.setMultiline(true);
log.info("Inside Proxy Creation");
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(creator);
return ProxyDataSourceBuilder
.create(actualDataSource())
.countQuery()
.name("MyDS")
.listener(listener)
.build();
}
}
When we run main application datasource-proxy is picked up but when we use #DataJpaTest it is not picking up. How to enable datasource-proxy in JUNIT test cases?
Edit::
Using Spring BeanPostProcessor to configure Proxy DataSource
#Slf4j
#Configuration
public class DataSourceBeanConfig implements BeanPostProcessor
{
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
if (bean instanceof DataSource)
{
System.out.println("AfterInitialization : " + beanName);
// use pretty formatted query with multiline enabled
PrettyQueryEntryCreator creator = new PrettyQueryEntryCreator();
creator.setMultiline(true);
log.info("Inside Proxy Creation");
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(creator);
return ProxyDataSourceBuilder.create((DataSource) bean).countQuery()
.name("MyDS").listener(listener).build();
}
return bean; // you can return any other object as well
}
}
Here is the solution we need to create TestConfiguration to use in #DataJpaTest
#RunWith(SpringRunner.class)
#DataJpaTest
public class DataTestJPA
{
#TestConfiguration
static class ProxyDataSourceConfig implements BeanPostProcessor
{
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException
{
if (bean instanceof DataSource)
{
return ProxyDataSourceBuilder
.create((DataSource) bean)
.countQuery()
.name("MyDS")
.build();
// #formatter:on
}
return bean; // you can return any other object as well
}
}
}

Spring jdbc configuration

I have been trying to implement a web service using spring. This webservice will provide data access to a mySQL database using JDBC. I am trying to not use any xml configuration files, so I have come across a problem trying to connect to the database.
I am following the tutorial: http://spring.io/guides/tutorials/rest/ but I changed a few things along the way.
Now that I am trying to implement the connection with the database I get an error when trying to execute the tomcat instance, and I guess the problem is within the configurations.
Here follows some of my code:
Datasource configuration:
#Configuration
#Profile("mySQL")
#PropertySource("classpath:/services.properties")
public class MySQLDataSourceConfiguration implements DataSourceConfiguration{
#Inject
private Environment environment;
#Bean
public DataSource dataSource() throws Exception {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setPassword(environment.getProperty("dataSource.password"));
dataSource.setUrl(environment.getProperty("dataSource.url"));
dataSource.setUsername(environment.getProperty("dataSource.user"));
dataSource.setDriverClassName(environment.getPropertyAsClass("dataSource.driverClass", Driver.class).getName());
return dataSource;
}
}
the file service.properties is where I keep my configurations for the database, so when I desire to change the database I will just have to change 4 fields.
The JDBCConfiguration class for the setup of the JDBCtemplate
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:/services.properties")
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSourceConfiguration dataSourceConfiguration;
#Inject
private Environment environment;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSourceConfiguration.dataSource());
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}
Then there is the Repository, that recieves the template.
#Transactional
#Repository
#Qualifier("jdbcRepository")
public class JdbcIndividualRepository implements IndividualsRepository{
private static final Logger LOG = LoggerFactory.getLogger(JdbcIndividualRepository.class);
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
public JdbcIndividualRepository(DataSource jdbcDataSource) {
LOG.info("JDBCRepo arg constructor");
this.jdbcTemplate = new JdbcTemplate(jdbcDataSource);
}
#Override
public Individual save(Individual save) {
String sql = "INSERT INTO Individual(idIndividual, Name) VALUES(?,?)";
this.jdbcTemplate.update(sql, save.getId(), save.getName());
return save;
}
#Override
public void delete(String key) {
String sql = "DELETE FROM Individual WHERE idIndividual=?";
jdbcTemplate.update(sql, key);
}
#Override
public Individual findById(String key) {
String sql = "SELECT i.* FROM Individual i WHERE i.idIndividual=?";
return this.jdbcTemplate.queryForObject(sql, new IndividualRowMapper(), key);
}
#Override
public List<Individual> findAll() {
String sql = "SELECT * FROM Individual";
return new LinkedList<Individual>(this.jdbcTemplate.query(sql, new IndividualRowMapper()));
}
}
Then I register the jdbc configuration in the initializer class when creating the root context of the application as follows:
private WebApplicationContext createRootContext(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(CoreConfig.class, SecurityConfig.class, JdbcConfiguration.class);
rootContext.refresh();
servletContext.addListener(new ContextLoaderListener(rootContext));
servletContext.setInitParameter("defaultHtmlEscape", "true");
return rootContext;
}
However, the Tomcat server wont run because it can't autowire the class MySQLDataSourceConfiguration.
Anyone knows what the problem might be? I can give more details on the code, but the question is already really large.
Appreciate any kind of help!
Cheers
EDIT
Solved changing the JdbcConfiguration class to:
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:/services.properties")
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSource dataSource;
#Inject
private Environment environment;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSource);
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public IndividualsRepository createRepo(){
return new JdbcIndividualRepository(dataSource);
}
}
Remove
#Autowired
private DataSourceConfiguration dataSourceConfiguration;
Because that's not how it's supposed to be used. Instead add to the same class the following:
#Autowired DataSource dataSource;
and use it like this: new JdbcTemplate(dataSource);
Also, try adding #ComponentScan to JdbcConfiguration class. From what I see in your code the class JdbcIndividualRepository is not picked up by anything.
In your class JdbcConfiguration, you are trying to autowire DataSourceConfiguration. I'm not really sure if that's possible - typically you should try to autwire the DataSource, not the DataSourceConfiguration.
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSource dataSource;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSource);
}
Also if you have several DataSources and you're using Spring profiles to separate them, it's easier to provide all the DataSource beans in one file and annotate each bean with a different profile:
#Configuration
public class DataSourceConfig {
#Bean
#Profile("Test")
public DataSource devDataSource() {
.... configure data source
}
#Bean
#Profile("Prod")
public DataSource prodDataSource() {
... configure data source
}

Resources