Spring Boot & MongoDB how to remove the '_class' column? - spring-data

When inserting data into MongoDB Spring Data is adding a custom "_class" column, is there a way to eliminate the "class" column when using Spring Boot & MongoDB?
Or do i need to create a custom type mapper?
Any hints or advice?

Dave's answer is correct. However, we generally recommend not do this (that's why it's enabled by default in the first place) as you effectively throw away to persist type hierarchies or even a simple property set to e.g. Object. Assume the following type:
#Document
class MyDocument {
private Object object;
}
If you now set object to a value, it will be happily persisted but there's no way you can read the value back into it's original type.

A more up to date answer to that question, working with embedded mongo db for test cases:
I quote from http://mwakram.blogspot.fr/2017/01/remove-class-from-mongo-documents.html
Spring Data MongoDB adds _class in the mongo documents to handle
polymorphic behavior of java inheritance. If you want to remove _class
just drop following Config class in your code.
package com.waseem.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
#Configuration
public class MongoConfig {
#Autowired MongoDbFactory mongoDbFactory;
#Autowired MongoMappingContext mongoMappingContext;
#Bean
public MappingMongoConverter mappingMongoConverter() {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
}

Here is a slightly simpler approach:
#Configuration
public class MongoDBConfig implements InitializingBean {
#Autowired
#Lazy
private MappingMongoConverter mappingMongoConverter;
#Override
public void afterPropertiesSet() throws Exception {
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
}
}

You can remove the _class by following code. You can use this in your mongo configuration class.
#Bean
public MongoTemplate mongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return new MongoTemplate(databaseFactory, converter);
}

I think you need to create a #Bean of type MongoTemplate and set the type converter explicitly. Details (non-Boot but just extract the template config): http://www.mkyong.com/mongodb/spring-data-mongodb-remove-_class-column/

Similar to RZet but avoids inheritance:
#Configuration
public class MongoConfiguration {
#Autowired
private MappingMongoConverter mappingMongoConverter;
// remove _class
#PostConstruct
public void setUpMongoEscapeCharacterConversion() {
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
}
}

A simple way (+ for ReactiveMongoTemplate):
#Configuration
public class MongoDBConfig {
#Autowired
private MongoClient mongoClient;
#Value("${spring.data.mongodb.database}")
private String dbName;
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate() {
ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoClient, dbName);
MappingMongoConverter converter = (MappingMongoConverter) template.getConverter();
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
converter.afterPropertiesSet();
return template;
}
}

Add a converter to remove class.
MappingMongoConverter converter =
new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);
return mongoTemplate;
`

The correct answer above seems to be using a number of deprecated dependencies. For example if you check the code, it mentions MongoDbFactory which is deprecated in the latest Spring release. If you happen to be using MongoDB with Spring-Data in 2020, this solution seems to be older. For instant results, check this snippet of code. Works 100%.
Just Create a new AppConfig.java file and paste this block of code. You'll see the "_class" property disappearing from the MongoDB document.
package com.reddit.redditmain.Configuration;
import org.apache.naming.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
#Configuration
public class AppConfig {
#Autowired
MongoDatabaseFactory mongoDbFactory;
#Autowired
MongoMappingContext mongoMappingContext;
#Bean
public MappingMongoConverter mappingMongoConverter() {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
}

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import
org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import
org.springframework.data.mongodb.core.convert.MappingMongoConverter;
#Configuration
public class MongoConfigWithAuditing {
#Bean
#Primary
public MongoTemplate mongoTemplate(MongoDatabaseFactory
mongoDatabaseFactory, MappingMongoConverter mappingMongoConverter) {
// this is to avoid saving _class to db
mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDatabaseFactory, mappingMongoConverter);
return mongoTemplate;
}
}

Spring Boot 3 with reactive mongo.
package es.dmunozfer.trading.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Configuration
#EnableReactiveMongoRepositories("es.dmunozfer.trading.repository")
public class MongoConfig extends AbstractReactiveMongoConfiguration {
#Autowired
private MongoProperties mongoProperties;
#Override
protected String getDatabaseName() {
return mongoProperties.getDatabase();
}
#Bean
#Override
public MappingMongoConverter mappingMongoConverter(ReactiveMongoDatabaseFactory databaseFactory, MongoCustomConversions customConversions,
MongoMappingContext mappingContext) {
MappingMongoConverter converter = super.mappingMongoConverter(databaseFactory, customConversions, mappingContext);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
}

I'm leaving this answer here in case someone wants to remove the _class from kotlin and update it a bit since the previous answers have several deprecated dependencies.
import org.springframework.beans.factory.BeanFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.mongodb.MongoDatabaseFactory
import org.springframework.data.mongodb.core.convert.DbRefResolver
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper
import org.springframework.data.mongodb.core.convert.MappingMongoConverter
import org.springframework.data.mongodb.core.mapping.MongoMappingContext
#Configuration
internal class SpringMongoConfig {
#Bean
fun mappingMongoConverter(
factory: MongoDatabaseFactory, context: MongoMappingContext,
beanFactory: BeanFactory
): MappingMongoConverter {
val dbRefResolver: DbRefResolver = DefaultDbRefResolver(factory)
val mappingConverter = MappingMongoConverter(dbRefResolver, context)
mappingConverter.setTypeMapper(DefaultMongoTypeMapper(null))
return mappingConverter
}
}

Related

Spring Data Entity UUID is stored as legacy UUID in MongoDB

I'm dumping some data into my MongoDb and generate a UUID on the way. In the collection, this UUID field is stored as LUUID (legacy UUID - type 3) and I don't know how to avoid this, because I would want the format to be the standard UUID (type 4).
Entity:
#Document(collection = "sms")
public class SmsEntity {
...
private UUID ubmMessageSid; // <- this field gets stored as LUUID
...
public static class Builder {
...
private UUID ubmMessageSid;
...
public Builder ubmMessageSid(UUID ubmMessageSid) {
this.ubmMessageSid = ubmMessageSid;
return this;
}
public SmsEntity build() {return new SmsEntity(this);}
}
}
Repo:
#Repository
public interface SmsRepository extends CrudRepository<SmsEntity, String> {
}
Service storing this entity:
...
var ubmId = UUID.randomUUID();
var smsEntity = SmsEntity.builder()
.ubmMessageSid(ubmId)
...
.build();
repository.save(smsEntity);
Anything I have to annotate or configure to store the UUID as Binary/type4?
In Spring Boot 2, for Spring MongoDB 3.x, you can set this using the autoconfiguration properties:
# Options: unspecified, standard, c_sharp_legacy, java_legacy, python_legacy
spring.data.mongodb.uuid-representation=standard
On version 3.x of Spring Data Mongo MongoClient has been replaced by MongoClientSettings:
CodecRegistry codecRegistry =
CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.JAVA_LEGACY)),
MongoClientSettings.getDefaultCodecRegistry());
return new MongoClient(new ServerAddress(address, port), MongoClientSettings.builder().codecRegistry(codecRegistry).build());
You can set the UUID codec in your Mongo config. That will persist your UUIDs with type 4 codec. The code you need to do that is the following:
CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
MongoClient.getDefaultCodecRegistry());
return new MongoClient(new ServerAddress(address, port), MongoClientOptions.builder().codecRegistry(codecRegistry).build());
Here is the complete class just in case:
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ServerAddress;
import org.bson.UuidRepresentation;
import org.bson.codecs.UuidCodec;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
#EnableAutoConfiguration
#EnableMongoRepositories(basePackages = "com.yourpackage.repositories")
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${mongo.database}")
String database;
#Value("${mongo.address}")
String address;
#Value("${mongo.port}")
Integer port;
#Override
protected String getDatabaseName() {
return database;
}
#Override
public MongoClient mongoClient() {
CodecRegistry codecRegistry =
CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
MongoClient.getDefaultCodecRegistry());
return new MongoClient(new ServerAddress(address, port), MongoClientOptions.builder().codecRegistry(codecRegistry).build());
}
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(localValidatorFactoryBean());
}
}
Attention: With the new Spring Boot Version 2.2.0.RELEASE AbstractMongoConfiguration becomes deprecated and this is not working anymore. I made a post for that, maybe it's a bug or maybe someone knows the answer: Spring Boot Standard UUID codec not working with AbstractMongoClientConfiguration
In my case the property in the application.yml didn't work as well as the one using CodecRegistries. My working solution is:
MongoConfig.java:
#Bean
public MongoClient mongoClient() {
MongoClientSettings.Builder builder = MongoClientSettings.builder();
builder.uuidRepresentation(UuidRepresentation.JAVA_LEGACY);
return MongoClients.create(builder.build());
}

HikariCP for MongoDB in springboot

I am looking to create a connection pool - for mongoDB in Springboot. I am currently making use of Springdata Mongo repositories to connect to DB and collections but unsure of how to create the datapool connection
Here is the current implementation
PersonRepository.java
package com.test.TestAPI.repository;
import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.test.TestAPI.dto.Person;
#Repository
public interface PersonRepository extends MongoRepository<Person, String> {
}
PersonService
package com.test.TestAPI.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.TestAPI.repository.PersonRepository;
import com.test.TestAPI.dto.Person;
#Service
public class PersonService {
#Autowired
private PersonRepository personRepo;
public List<Person> findAllPersons() {
return personRepo.findAll();
}
public Person createPerson(Person person) {
return personRepo.save(person);
}
}
PersonController
package com.test.TestAPI.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.test.TestAPI.service.impl.PersonService;
import com.test.TestAPI.dto.Person;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
#RestController
#RequestMapping(path="/v1/personController")
#Api(value="Controller for Person document")
public class PersonController {
#Autowired
PersonService service;
#GetMapping("/getAllPersons")
#ApiOperation(produces = MediaType.APPLICATION_JSON_VALUE, httpMethod = "GET", response = List.class,
value = "getAllPersons from the database", notes = "Sample note")
public ResponseEntity<List<Person>> getAllPersons(){
List<Person> personList = service.findAllPersons();
return new ResponseEntity<List<Person>>(personList, HttpStatus.OK);
}
}
SimpleCommandLineConfig
package com.test.TestAPI.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.stereotype.Component;
import com.test.TestAPI.repository.PersonRepository;
import com.test.TestAPI.dto.Person;
#Component
#Order(3)
public class SimpleCommandLineConfig implements CommandLineRunner {
#Autowired
PersonRepository repo;
#Override
public void run(String... args) throws Exception {
// TODO Auto-generated method stub
System.out.println("third command line runner");
System.out.println(repo.save(new Person("rr","rr",4)));
}
}
App.java
package com.test.TestAPI.main;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Hello world!
*
*/
#SpringBootApplication
#ComponentScan(basePackages= {"com.test.TestAPI"})
#EnableMongoRepositories(basePackages= {"com.test.TestAPI.repository"})
public class App
{
public static void main(String[] args) throws Exception {
SpringApplication.run(App.class, args);
}
}
Also, I would like to know if spring data repos like a custom repo for MongoDB takes care of connection pool mechanism? How does connection pooling happen in that case? Could you please help me on this
I think I found an answer for this. Just like we use JDBC templates for RDBMS to store the databases, spring provides something called MongoTemplates which forms a connection pool based on the db configuration given
Here is a sample implementation
MongoClientFactory.java
public #Bean MongoClientFactoryBean mongo() throws Exception {
MongoClientFactoryBean mongo = new MongoClientFactoryBean();
mongo.setHost("localhost");
MongoClientOptions clientOptions = MongoClientOptions.builder().applicationName("FeddBackAPI_DB")
.connectionsPerHost(2000)
.connectTimeout(4000)
//.maxConnectionIdleTime(1000000000)
.maxWaitTime(3000)
.retryWrites(true)
.socketTimeout(4000)
.sslInvalidHostNameAllowed(true)//this is very risky
.build();
mongo.setMongoClientOptions(clientOptions);
return mongo;
}
DataSourceConfig.java (Another config class. Same config class can also be used to define all beans)
package com.fmr.FeedBackAPI.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
#Configuration
#Import(value=MongoClientFactory.class)
public class DataSourceConfig {
#Autowired
Mongo mongo;
#Autowired
Environment env;
#Bean
public String test() {
System.out.println("mongo"+mongo);
return "rer";
}
private MongoTemplate mongoTemplate() {
MongoDbFactory factory = new SimpleMongoDbFactory((MongoClient) mongo, "mongo_test");
MongoTemplate template = new MongoTemplate(factory);
return template;
}
#Bean
#Qualifier(value="customMongoOps")
public MongoOperations mongoOps() {
MongoOperations ops = mongoTemplate();
return ops;
}
#Bean
public MongoDbFactory factory() {
MongoDbFactory factory = new SimpleMongoDbFactory((MongoClient) mongo, "mongo_test");
return factory;
}
// #Bean
// public GridFsTemplate gridFsTemplate() {
// return new GridFsTemplate(mongo, converter)
// }
#Bean
public javax.sql.DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setMaximumPoolSize(100);
// ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
// ds.setJdbcUrl(env.getProperty("spring.datasource.url"));;
//ds.setUsername(env.getProperty("spring.datasource.username"));
//ds.setPassword(env.getProperty("spring.datasource.password"));
ds.addDataSourceProperty("cachePrepStmts", true);
ds.addDataSourceProperty("prepStmtCacheSize", 250);
ds.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
ds.addDataSourceProperty("useServerPrepStmts", true);
return ds;
}
}
Now autowire the template/mongoOperations in you dao implementation or service and use it
package com.fmr.FeedBackAPI.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import com.fmr.FeedBackAPI.dto.ConfigDTO;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
#Service
public class PlanConfigService {
#Autowired
#Qualifier(value="customMongoOps")
MongoOperations mongoOps;
public List<ConfigDTO> createConfigDTOList(List<ConfigDTO> configDTOList) {
List<ConfigDTO> configList = new ArrayList<>();
if(!mongoOps.collectionExists(ConfigDTO.class)) {
mongoOps.createCollection("ConfigDTO_table");
}
//create the configDTOList
mongoOps.insert(configDTOList, ConfigDTO.class);
configList = mongoOps.findAll(ConfigDTO.class, "ConfigDTO_table");
return configList;
}
public List<ConfigDTO> createConfigDTO(ConfigDTO configDTO) {
List<ConfigDTO> configList = new ArrayList<>();
if(!mongoOps.collectionExists(ConfigDTO.class)) {
mongoOps.createCollection("ConfigDTO_table");
}
//create the configDTOList
mongoOps.save(configDTO);
configList = mongoOps.find(query(where("planId").is(configDTO.getPlanId())), ConfigDTO.class,"ConfigDTO_table");
return configList;
}
}
Here is the application.properties (this is the default instance running in local)
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=mongo_test
spring.data.mongodb.repositories=true
#
spring.datasource.url=jdbc:mongodb://localhost:27017/mongo_test

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

Reading Multiple excel File Using Spring Batch Extension

I am trying to read multiple excel files using Spring-Bath-Excel. In my scenario i don't know i advance how many files client will process i.e. if data would be very large, excel file will be split into multiple files like records1.xls ,records2.xls, records3.xls..
Is there any kind of MultiResourceItemReader available in Spring-Batch-Excel? I tried to set multiple resources at run time and also tried to use the patterns records*.xls but PoiItemReader did't allow me to do that .
I am using PoiItemReader for that .
To Read Multiple Excel
package com.abc.ingestion.job.dci;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.extensions.excel.RowMapper;
import org.springframework.batch.extensions.excel.streaming.StreamingXlsxItemReader;
import org.springframework.batch.extensions.excel.support.rowset.DefaultRowSetFactory;
import org.springframework.batch.extensions.excel.support.rowset.StaticColumnNameExtractor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.MultiResourceItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
// Create input folder in resources
#Value("input/DCI*.xlsx")
private Resource[] inputResources;
#Bean
public MultiResourceItemReader<CouncilMapper> multiResourceItemReader() {
MultiResourceItemReader<CouncilMapper> resourceItemReader = new MultiResourceItemReader<>();
resourceItemReader.setResources(inputResources);
resourceItemReader.setDelegate(reader());
return resourceItemReader;
}
private RowMapper<CouncilMapper> excelRowMapper() {
return new Mapper();
}
#SuppressWarnings({ "rawtypes", "unchecked" })
#Bean
public StreamingXlsxItemReader<CouncilMapper> reader() {
final String[] COLUMNS = {"Reg_Type","RegUnder","registration_no","registration_date","course","Other_Course","LRegDate","council_name","full_name","CatName","Other_Category","father_name","mother_name","gender","nationality","date_of_birth","place_of_birth","permanent_address","business_address","current_city","current_state","permanent_city","mobile_number","OfficialTelephone","email","aadhar_number","PanNo","IsDeleted","CreatedDate","UpdatedDate","speciality_name"};
var factory = new DefaultRowSetFactory();
factory.setColumnNameExtractor(new StaticColumnNameExtractor(COLUMNS));
StreamingXlsxItemReader<CouncilMapper> reader = new StreamingXlsxItemReader<>();
reader.setLinesToSkip(1);
reader.setRowSetFactory(factory);
reader.setRowMapper(excelRowMapper());
return reader;
}
#Bean
ItemWriter<CouncilMapper> writer() {
return new Writer();
}
#Bean
public Job readFilesJob() {
return jobBuilderFactory
.get("readFilesJob")
.incrementer(new RunIdIncrementer())
.start(excelFileStep())
.build();
}
#Bean
public Step excelFileStep() {
return stepBuilderFactory.get("excelFileStep")
.<CouncilMapper, CouncilMapper>chunk(5)
.reader(multiResourceItemReader())
.writer(writer())
.build();
}
}
Mapper Class
package com.abc.ingestion.job.dci;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.batch.extensions.excel.RowMapper;
import org.springframework.batch.extensions.excel.support.rowset.RowSet;
public class Mapper implements RowMapper<CouncilMapper> {
#Override
public CouncilMapper mapRow(RowSet rowSet) throws Exception {
var rowSetMetaData = rowSet.getMetaData();
String[] columnNames = rowSetMetaData.getColumnNames();
String[] rowData = rowSet.getCurrentRow();
var mapper = new ObjectMapper();
Map<String, String> excelData = new HashMap<>();
IntStream.range(0, columnNames.length).forEach(index -> excelData.put(columnNames[index], rowData[index]));
return mapper.convertValue(excelData, CouncilMapper.class);
}
}

Spring-boot, unable to autowire a class.No default constructor found Exception is raised

I am new to spring-boot. After i moved a class to different package (other the one contains 'Application'), Could not instantiate bean class: No default constructor found Exception is raised.
Before (workable code)
package com.server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
#Configuration
#ComponentScan(basePackages = {"com.server" })
#EnableAutoConfiguration
#Profile({ "default" })
#Controller
public class Application {
private static Log logger = LogFactory.getLog(Application.class);
public static void main(String[] args) {
logger.info("Starting Application...");
SpringApplication.run(Application.class, args);
}
}
A piece of code from http://bitwiseor.com/2013/09/20/creating-test-services-with-spring-boot/
package com.server;
import java.util.Collections;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Configuration
#Controller
#Profile({ "default" })
class Franchise {
private JdbcTemplate jdbcTemplate;
#Autowired
public Franchise(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
#ResponseBody
#RequestMapping("/api/franchise/{id}")
String franchiseId(#PathVariable Long id) {
try {
return jdbcTemplate.queryForMap("SELECT id, title FROM franchises WHERE id=?", id).toString();
} catch(EmptyResultDataAccessException ex) {
return Collections.EMPTY_MAP.toString();
}
}
#ResponseBody
#RequestMapping("/api/franchise")
String franchises() {
try {
return jdbcTemplate.queryForList("SELECT id, title FROM franchises").toString();
} catch(EmptyResultDataAccessException ex) {
return Collections.EMPTY_MAP.toString();
}
}
}
I am able to bring up the server when class 'Application' and 'Franchise' are located in the same package. However, when I moved the class 'Franchise' into another package as shown below, I've got this exception: Could not instantiate bean class: No default constructor found Exception is raised.
package com.server.api;
import java.util.Collections;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Configuration
#Controller
#Profile({ "default" })
class Franchise {
private JdbcTemplate jdbcTemplate;
#Autowired
public Franchise(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
#ResponseBody
#RequestMapping("/api/franchise/{id}")
String franchiseId(#PathVariable Long id) {
try {
return jdbcTemplate.queryForMap("SELECT id, title FROM franchises WHERE id=?", id).toString();
} catch(EmptyResultDataAccessException ex) {
return Collections.EMPTY_MAP.toString();
}
}
#ResponseBody
#RequestMapping("/api/franchise")
String franchises() {
try {
return jdbcTemplate.queryForList("SELECT id, title FROM franchises").toString();
} catch(EmptyResultDataAccessException ex) {
return Collections.EMPTY_MAP.toString();
}
}
}
How can I solve this problem if I wanted to move this class into a different package?
Thanks!
Edit: I found a solution
When I removed the following tag, I am able to put the class into separate package.
#Configuration
#Profile({ "default" })
But I have no idea why...
It looks to me like your Franchise class is package private (the default visibility for a java class). That would explain everything (and no need to involve Spring or anything other than a compiler). To fix it just declare your class to be "public".
Marten is also correct that #Configuration is probably not want you mean for the Franchise (but in this case it's harmless).