Using JdbcTemplate with Named parameters in spring batch - spring-batch

i'm trying to pass a parameter to my query in spring batch. I decided to create a tasklet and use JdbcTemplate as follows ...
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext)
throws EpsilonBatchBusinessException {
LOGGER.debug("Enter execute.");
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.query(queryString,
new PreparedStatementSetter() {
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setInt(1, runNumber);
}
},
rowMapper);
LOGGER.debug("Exit execute.");
return RepeatStatus.FINISHED;
}
So am injecting to this bean a dataSource, queryString, rowMapper object, and the parameter (runNumber) .. This tasklet will be called within a step to create a list. I usually pass the row mapper to JdbcCursorItemReader spring bean and wouldn't write a tasklet, but my query string needs a parameter hence am writing this tasklet. Am just not sure if this tasklet will do the trick as with JdbcCursorItemReader? Your input wil be appreciated

A better option would be to use the JdbcCursorItemReader and write a custom PreparedStatementSetter.
The PreparedStatementSetter interface is very simple; pretty much all the code you'd need to write is below. Once the setter is written, all you need to do is configure it as a new bean with the runNumber value injected in the config, and then inject that bean into a JdbcCursorItemReader. This allows you to use all the usual ItemReaders and ItemWriters instead of having to implement everything by hand in a Tasklet.
package com.foo;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.jdbc.core.PreparedStatementSetter;
public class YourParamSetter implements PreparedStatementSetter {
private int runNumber;
public void setValues(PreparedStatement ps) throws SQLException {
ps.setInt(1, runNumber);
}
public void setRunNumber(int runNumber) {
this.runNumber = runNumber;
}
public int getRunNumber() {
return runNumber;
}
}

Related

SpringBoot: Create document in mongodb on startup if not exists

I have a small service on SpringBoot and Mongodb as a DB.
I need to be able create a small collection with one document ( very basic: id, name, status) on startup. An analog of sql create table if not exists, but for mongo. How do I do that?
I tried to initialize values in the document attributes, but it didn't help.
Currently, collection and the document appear only if I use API to add it.
You may want to use something like ApplicationRunner or CommandLineRunner which can be defined as a bean.
Example:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication .class, args);
}
#Bean
public CommandLineRunner initialize(MyRepository myRepository) {
return args -> {
// Insert elements into myRepository
};
}
}
Both CommandLineRunner and ApplicationRunner are functional interfaces, so we can use a lambda for them. Spring Boot will execute them at the startup of the application.
You can leverage the spring internal event mechanism.
When your application is ready, spring triggers the event ApplicationReadyEvent
You can listen to this event and init your collection:
#Component
public class DataInit implements ApplicationListener<ApplicationReadyEvent> {
private final MyRepository myRepository;
public DataInit(MyRepository myRepository) {
this.myRepository = myRepository;
}
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// init data
}
}

Should Job/Step/Reader/Writer all be bean?

As fars as all the examples from the Spring Batch reference doc , I see that those objects like job/step/reader/writer are all marked as #bean, like the following:
#Bean
public Job footballJob() {
return this.jobBuilderFactory.get("footballJob")
.listener(sampleListener())
...
.build();
}
#Bean
public Step sampleStep(PlatformTransactionManager transactionManager) {
return this.stepBuilderFactory.get("sampleStep")
.transactionManager(transactionManager)
.<String, String>chunk(10)
.reader(itemReader())
.writer(itemWriter())
.build();
}
I have a scenario that the server side will receive requests and run job concurrently(different job names or same job name with different jobparameters). The usage is to new a job object(including steps/reader/writers) in concurrent threads, so I propabaly will not state the job method as #bean and new a job each time.
And there is actually a differenence on how to transmit parameters to object like reader. If using #bean , parameters must be put in e.g. JobParameters to be late binding into object using #StepScope, like the following example:
#StepScope
#Bean
public FlatFileItemReader flatFileItemReader(#Value(
"#{jobParameters['input.file.name']}") String name) {
return new FlatFileItemReaderBuilder<Foo>()
.name("flatFileItemReader")
.resource(new FileSystemResource(name))
}
If not using #bean , I can just transmit parameter directly with no need to put data into JobParameter,like the following
public FlatFileItemReader flatFileItemReader(String name) {
return new FlatFileItemReaderBuilder<Foo>()
.name("flatFileItemReader")
.resource(new FileSystemResource(name))
}
Simple test shows that no #bean works. But I want to confirm formally:
1、 Is using #bean at job/step/reader/writer mandatory or not ?
2、 if it is not mandatory, when I new a object like reader, do I need to call afterPropertiesSet() manually?
Thanks!
1、 Is using #bean at job/step/reader/writer mandatory or not ?
No, it is not mandatory to declare batch artefacts as beans. But you would want to at least declare the Job as a bean to benefit from Spring's dependency injection (like injecting the job repository reference into the job, etc) and be able to do something like:
ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfig.class);
Job job = context.getBean(Job.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
jobLauncher.run(job, new JobParameters());
2、 if it is not mandatory, when I new a object like reader, do I need to call afterPropertiesSet() manually?
I guess that by "when I new a object like reader" you mean create a new instance manually. In this case yes, if the object is not managed by Spring, you need to call that method yourself. If the object is declared as a bean, Spring will call
the afterPropertiesSet() method automatically. Here is a quick sample:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class TestAfterPropertiesSet {
#Bean
public MyBean myBean() {
return new MyBean();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(TestAfterPropertiesSet.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.sayHello();
}
static class MyBean implements InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean.afterPropertiesSet");
}
public void sayHello() {
System.out.println("Hello");
}
}
}
This prints:
MyBean.afterPropertiesSet
Hello

Spring Batch, JpaRepository and Rollback

I have a Spring Batch application(Spring Boot 2.3.5.RELEASE) that uses a JpaRepository to insert some custom log messages into a database as Spring Batch is processing. This is separate from the out of the box Spring Batch tables. Seems that when I throw an exception from my ItemProcessorAdapter, it is caught by the ItemProcessListener onProcessError() method. In this method I am performing a JpaRepository save() and flush(). No errors are logged, but once I leave this method the JpaRepository does a rollback.
Is this normal behavior? How can I get around it?
When using JpaRepository, is there a way to set a #Transactional(noRollbackFor = {xxxException.class})? I tried this and it seemed to have no effect.
Sample code snippet is below.
#Configuration
public class BatchJobConfiguration {
//Omitted for clarity....
#Bean
#StepScope
public CompositeItemProcessor<Decision,Decision> itemProcessor() {
CompositeItemProcessor<Decision,Decision> itemProcessor = new CompositeItemProcessor<>();
itemProcessor.setDelegates(Arrays.asList(
decisionValidatingItemProcessor(),
myItemProcessor(null)
));
return itemProcessor;
} // end itemProcessor()
#Bean
public BeanValidatingItemProcessor<Decision> decisionValidatingItemProcessor() {
BeanValidatingItemProcessor<Decision> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
beanValidatingItemProcessor.setFilter(true);
return beanValidatingItemProcessor;
} // end decisionValidatingItemProcessor()
#Bean
public ItemProcessorAdapter<Decision,Decision> myItemProcessor(DecisionProcessingService service) {
ItemProcessorAdapter<Decision,Decision> adapter = new ItemProcessorAdapter<>();
adapter.setTargetObject(service);
adapter.setTargetMethod("processDecision");
return adapter;
}
#Bean
#StepScope
public DecisionItemProcessListener decisionItemProcessListener() {
return new DecisionItemProcessListener(mpJpaRepository);
}
}
#Service
public class DecisionProcessingService {
public Decision processDecision(Decision decision) throws BatchException {
....
throw new BatchException("An error occurred");
}
}
public class DecisionItemProcessListener implements ItemProcessListener<Decision,Decision> {
private MyJpaRepository mpJpaRepository;
public DecisionItemProcessListener(MyJpaRepository mpJpaRepository) {
this.mpJpaRepository = mpJpaRepository;
}
....
#Override
public void onProcessError(Decision decision, Exception e) {
MyEntityObject obj = MyEntityObject.builder()
.msg(e.getMessage())
.build();
mpJpaRepository.save(obj);
mpJpaRepository.flush();
// after this, the insert above is rolled back.
} // end onProcessError()
}
The callback you are using here ItemProcessListener#onProcessError is called with-in a transaction (driven by Spring Batch) that is going to be rolled-back due to the exception thrown by the item processor.
If you want to save data in that method, you need to use a new transaction (use the REQUIRES_NEW propagation).
EDIT: I shared a minimal complete example here: https://github.com/benas/spring-batch-lab/tree/master/issues/so64913980.

How do I get Spring Batch Job ContextId in ItemProcessor or ItemWriter?

I need to store Job ExecutionId as one of the fields of Entity. (I am using JpaItemWriter)
One of topic here explains from StepExcecution, I can get StepContext -> JobExecution. In that case how to get StepExecution?
(I have no need to pass any data from one step to another, all I need is JobExecuionId)
Thanks for help,
Muneer Ahmed
I would suggest you use a processor that updates your Entity with value. If your processors directly implements ItemProcessor<T> then you will not automatically get the StepExecution. To get the StepExecution, do 1 of the following;
- implement StepExecutionListener and set it as a variable from the beforeStep method
- create a method called [something](StepExecution execution) and annotate with #BeforeStep
once you've injected the StepExecution via a listener, you can then get the jobExecutionId and set it into your entity
public class MyEntityProcessor implements ItemProcessor<MyEntity, MyEntity> {
private long jobExecutionId;
#BeforeStep
public void beforeStep(StepExecution stepExecution) {
jobExecutionId = stepExecution.getJobExecutionId();
}
#Override
public MyEntity process(MyEntity item) throws Exception {
//set the values
item.setJobExecutionId(jobExecutionId);
//continue
return item;
}
}
We can set the scope as job using #Scope("job") of Itemprocessor and can easly get JobExecution using #Value("#{jobExecution}") expression as below.
#Service
#Scope("job")
public class XsltTransformer implements ItemProcessor<Record, Record> {
#Value("#{jobExecution}")
private JobExecution jobExecution;
}
Scope as job using #Scope("job") of and get JobExecution using #Value("#{jobExecution}")
You have to add the following:
#Value("#{stepExecution.jobExecution}")
private JobExecution jobExecution;
in your reader Writer or processor.
It will certainly work.
If you want to use #BeforeStep in MyEntityProcessor you must declare it like a listener
<batch:listeners>
<batch:listener ref="myEntityProcessor" />
</batch:listeners>
I had a similar issue when I wanted to get the JobInstanceID. Below is how I got StepExecution and then retreived JobExecution. I hope this helps.
public class FoobarItemProcessor implements ItemProcessor<Foobar, Foobar> {
private JobExecution jobExecution;
#BeforeStep
public void beforeStep(StepExecution stepExecution) {
jobExecution = stepExecution.getJobExecution();
}

Spring data MongoDb: MappingMongoConverter remove _class

The default MappingMongoConverter adds a custom type key ("_class") to each object in the database. So, if I create a Person:
package my.dto;
public class Person {
String name;
public Person(String name) {
this.name = name;
}
}
and save it to the db:
MongoOperations ops = new MongoTemplate(new Mongo(), "users");
ops.insert(new Person("Joe"));
the resulting object in the mongo will be:
{ "_id" : ObjectId("4e2ca049744e664eba9d1e11"), "_class" : "my.dto.Person", "name" : "Joe" }
Questions:
What are the implications of moving the Person class into a different namespace?
Is it possible not to pollute the object with the "_class" key; without writing a unique converter just for the Person class?
So here's the story: we add the type by default as some kind of hint what class to instantiate actually. As you have to pipe in a type to read the document into via MongoTemplate anyway there are two possible options:
You hand in a type the actual stored type can be assigned to. In that case we consider the stored type, use that for object creation. Classical example here is doing polymorphic queries. Suppose you have an abstract class Contact and your Person. You could then query for Contacts and we essentially have to determine a type to instantiate.
If you - on the other hand - pass in a completely different type we'd simply marshal into that given type, not into the one stored in the document actually. That would cover your question what happens if you move the type.
You might be interested in watching this ticket which covers some kind of pluggable type mapping strategy to turn the type information into an actual type. This can serve simply space saving purposes as you might want to reduce a long qualified class name to a hash of a few letters. It would also allow more complex migration scenarios where you might find completely arbitrary type keys produced by another datastore client and bind those to Java types.
Here's my annotation, and it works.
#Configuration
public class AppMongoConfig {
public #Bean
MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(new Mongo(), "databasename");
}
public #Bean
MongoTemplate mongoTemplate() throws Exception {
//remove _class
MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);
return mongoTemplate;
}
}
If you want to disable _class attribute by default, but preserve polymorfism for specified classes, you can explictly define the type of _class (optional) field by configuing:
#Bean
public MongoTemplate mongoTemplate() throws Exception {
Map<Class<?>, String> typeMapperMap = new HashMap<>();
typeMapperMap.put(com.acme.domain.SomeDocument.class, "role");
TypeInformationMapper typeMapper1 = new ConfigurableTypeInformationMapper(typeMapperMap);
MongoTypeMapper typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Arrays.asList(typeMapper1));
MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
converter.setTypeMapper(typeMapper);
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);
return mongoTemplate;
}
This will preserve _class field (or whatever you want to name in construtor) for only specified entities.
You can also write own TypeInformationMapper for example based on annotations. If you annotate your document by #DocumentType("aliasName") you will keep polymorphism by keeping alias of class.
I have explained briefly it on my blog, but here is some piece of quick code:
https://gist.github.com/athlan/6497c74cc515131e1336
<mongo:mongo host="hostname" port="27017">
<mongo:options
...options...
</mongo:mongo>
<mongo:db-factory dbname="databasename" username="user" password="pass" mongo-ref="mongo"/>
<bean id="mongoTypeMapper" class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper">
<constructor-arg name="typeKey"><null/></constructor-arg>
</bean>
<bean id="mongoMappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<bean id="mongoConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
<constructor-arg name="mappingContext" ref="mongoMappingContext" />
<property name="typeMapper" ref="mongoTypeMapper"></property>
</bean>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
<constructor-arg name="mongoConverter" ref="mongoConverter" />
<property name="writeResultChecking" value="EXCEPTION" />
</bean>
While, Mkyong's answer still works, I would like to add my version of solution as few bits are deprecated and may be in the verge of cleanup.
For example : MappingMongoConverter(mongoDbFactory(), new MongoMappingContext()) is deprecated in favor of new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); and SimpleMongoDbFactory(new Mongo(), "databasename"); in favor of new SimpleMongoDbFactory(new MongoClient(), database);.
So, my final working answer without deprecation warnings is :
#Configuration
public class SpringMongoConfig {
#Value("${spring.data.mongodb.database}")
private String database;
#Autowired
private MongoDbFactory mongoDbFactory;
public #Bean MongoDbFactory mongoDBFactory() throws Exception {
return new SimpleMongoDbFactory(new MongoClient(), database);
}
public #Bean MongoTemplate mongoTemplate() throws Exception {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
// Remove _class
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return new MongoTemplate(mongoDBFactory(), converter);
}
}
Hope this helps people who would like to have a clean class with no deprecation warnings.
For Spring Boot 2.3.0.RELEASE it's more easy, just override the method mongoTemplate, it's already has all things you need to set type mapper. See the following example:
#Configuration
#EnableMongoRepositories(
// your package ...
)
public class MongoConfig extends AbstractMongoClientConfiguration {
// .....
#Override
public MongoTemplate mongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
// remove __class field from mongo
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return super.mongoTemplate(databaseFactory, converter);
}
// .....
}
This is my one line solution:
#Bean
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {
MongoTemplate mongoTemplate = new MongoTemplate(getMongoClient(), dbName);
((MappingMongoConverter)mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));//removes _class
return mongoTemplate;
}
I struggled a long time with this problem. I followed the approach from mkyong but when I introduced a LocalDate attribute (any JSR310 class from Java 8) I received the following exception:
org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [java.time.LocalDate] to type [java.util.Date]
The corresponding converter org.springframework.format.datetime.standard.DateTimeConverters is part of Spring 4.1 and is referenced in Spring Data MongoDB 1.7. Even if I used newer versions the converter didn't jump in.
The solution was to use the existing MappingMongoConverter and only provide a new DefaultMongoTypeMapper (the code from mkyong is under comment):
#Configuration
#EnableMongoRepositories
class BatchInfrastructureConfig extends AbstractMongoConfiguration
{
#Override
protected String getDatabaseName() {
return "yourdb"
}
#Override
Mongo mongo() throws Exception {
new Mongo()
}
#Bean MongoTemplate mongoTemplate()
{
// overwrite type mapper to get rid of the _class column
// get the converter from the base class instead of creating it
// def converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext())
def converter = mappingMongoConverter()
converter.typeMapper = new DefaultMongoTypeMapper(null)
// create & return template
new MongoTemplate(mongoDbFactory(), converter)
}
To summarize:
extend AbstractMongoConfiguration
annotate with EnableMongoRepositories
in mongoTemplate get converter from base class, this ensures that the type conversion classes are registered
#Configuration
public class MongoConfig {
#Value("${spring.data.mongodb.database}")
private String database;
#Value("${spring.data.mongodb.host}")
private String host;
public #Bean MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(new MongoClient(host), database);
}
public #Bean MongoTemplate mongoTemplate() throws Exception {
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(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 "Your Package Name";
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;
}
}
I'm using:
package YOUR_PACKAGE;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
#Configuration
public class MongoConfiguration {
#Autowired
private MappingMongoConverter mongoConverter;
#PostConstruct
public void setUpMongoEscapeCharacterAndTypeMapperConversion() {
mongoConverter.setMapKeyDotReplacement("_");
// This will remove _class: key
mongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
}
}
Btw: It is also replacing "." with "_"
you just need to add the #TypeAlias annotation to the class defintion over changing the type mapper
I've tried the solutions above, some of them don't work in combination with auditing, and none seems to set correctly the MongoCustomConversions
A solution that works for me is the following
#Configuration
public class MongoConfig {
#Bean
public MappingMongoConverter mappingMongoConverterWithCustomTypeMapper(
MongoDatabaseFactory factory,
MongoMappingContext context,
MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
mappingConverter.setCustomConversions(conversions);
/**
* replicate the way that Spring
* instantiates a {#link DefaultMongoTypeMapper}
* in {#link MappingMongoConverter#MappingMongoConverter(DbRefResolver, MappingContext)}
*/
CustomMongoTypeMapper customTypeMapper = new CustomMongoTypeMapper(
context,
mappingConverter::getWriteTarget);
mappingConverter.setTypeMapper(customTypeMapper);
return mappingConverter;
}
}
public class CustomMongoTypeMapper extends DefaultMongoTypeMapper {
public CustomMongoTypeMapper(
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
UnaryOperator<Class<?>> writeTarget) {
super(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext, writeTarget);
}
#Override
public TypeInformation<?> readType(Bson source) {
/**
* do your conversion here, and eventually return
*/
return super.readType(source);
}
}
As an alternative, you could use a BeanPostProcessor to detect the creation of a mappingMongoConverter, and add your converter there.
Something like
public class MappingMongoConverterHook implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("mappingMongoConverter" == beanName) {
((MappingMongoConverter) bean).setTypeMapper(new CustomMongoTypeMapper());
}
return bean;
}
}