Skip to content

Forced Sort Passing via sorts() Method When Building with MongoCursorItemReaderBuilder and Query #4860

Open
@apptie

Description

@apptie

Bug description
When building a MongoCursorItemReader using MongoCursorItemReaderBuilder with a Query object, the jsonQuery is null in MongoCursorItemReader.doOpen(), so it uses the passed Query.

In this case, MongoCursorItemReader.createQuery() is not called, which means the values specified in MongoCursorItemReaderBuilder.sorts() are ignored.

I confirmed that the Sort passed to the Builder is ignored through the following simple example code.

@Bean
public Job reportJob() {
    return new JobBuilder("reportJob", jobRepository)
            .start(reportStep())
            .build();
}

@Bean
public Step reportStep() {
    return new StepBuilder("reportStep", jobRepository)
            .<ReportData, ReportData>chunk(10, transactionManager)
            .reader(reportReader())
            .writer(reportWriter())
            .build();
}

@Bean
public MongoCursorItemReader<ReportData> reportReader() {
    Query query = new Query()
            .with(Sort.by(Sort.Direction.ASC, "timestamp"))
            .cursorBatchSize(10);

    return new MongoCursorItemReaderBuilder<ReportData>()
            .name("reportReader")
            .template(mongoTemplate)
            .collection("reportData")
            .query(query)
            .sorts(Map.of("timestamp", Sort.Direction.DESC)) // ignored 
            .targetType(ReportData.class)
            .build();
}

@Bean
public ItemWriter<ReportData> reportWriter() {
    return items -> items.forEach(data -> log.info("[result] : {}", data));
}

If sorts are not passed in MongoCursorItemReaderBuilder.sorts():

@Bean
public MongoCursorItemReader<ReportData> reportReader() {
    Query query = new Query()
            .with(Sort.by(Sort.Direction.ASC, "timestamp"))
            .cursorBatchSize(10);

    return new MongoCursorItemReaderBuilder<ReportData>()
            .name("reportReader")
            .template(mongoTemplate)
            .collection("reportData")
            .query(query)
            .targetType(ReportData.class)
            .build();
}

the following exception occurs:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reportJob' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'reportJob' threw exception with message: Error creating bean with name 'reportStep' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.core.Step]: Factory method 'reportStep' threw exception with message: Error creating bean with name 'reportReader' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.item.data.MongoCursorItemReader]: Factory method 'reportReader' threw exception with message: sorts map is required.
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351) ~[spring-boot-3.5.0.jar:3.5.0]
at com.example.batch.demo.DemoApplication.main(DemoApplication.java:10) ~[main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'reportJob' threw exception with message: Error creating bean with name 'reportStep' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.core.Step]: Factory method 'reportStep' threw exception with message: Error creating bean with name 'reportReader' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.item.data.MongoCursorItemReader]: Factory method 'reportReader' threw exception with message: sorts map is required.
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.7.jar:6.2.7]
... 20 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reportStep' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.core.Step]: Factory method 'reportStep' threw exception with message: Error creating bean with name 'reportReader' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.item.data.MongoCursorItemReader]: Factory method 'reportReader' threw exception with message: sorts map is required.
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.resolveBeanReference(ConfigurationClassEnhancer.java:425) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:396) ~[spring-context-6.2.7.jar:6.2.7]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.reportStep() ~[main/:na]
at com.example.batch.demo.ReportJobConfig.reportJob(ReportJobConfig.java:33) ~[main/:na]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.CGLIB$reportJob$0() ~[main/:na]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$FastClass$$1.invoke() ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:393) ~[spring-context-6.2.7.jar:6.2.7]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.reportJob() ~[main/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.7.jar:6.2.7]
... 23 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.core.Step]: Factory method 'reportStep' threw exception with message: Error creating bean with name 'reportReader' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.item.data.MongoCursorItemReader]: Factory method 'reportReader' threw exception with message: sorts map is required.
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.7.jar:6.2.7]
... 44 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reportReader' defined in class path resource [com/example/batch/demo/ReportJobConfig.class]: Failed to instantiate [org.springframework.batch.item.data.MongoCursorItemReader]: Factory method 'reportReader' threw exception with message: sorts map is required.
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:489) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1375) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1205) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.resolveBeanReference(ConfigurationClassEnhancer.java:425) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:396) ~[spring-context-6.2.7.jar:6.2.7]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.reportReader() ~[main/:na]
at com.example.batch.demo.ReportJobConfig.reportStep(ReportJobConfig.java:41) ~[main/:na]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.CGLIB$reportStep$1() ~[main/:na]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$FastClass$$1.invoke() ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:393) ~[spring-context-6.2.7.jar:6.2.7]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.reportStep() ~[main/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.7.jar:6.2.7]
... 47 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.data.MongoCursorItemReader]: Factory method 'reportReader' threw exception with message: sorts map is required.
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-6.2.7.jar:6.2.7]
... 68 common frames omitted
Caused by: java.lang.IllegalArgumentException: sorts map is required.
at org.springframework.util.Assert.notNull(Assert.java:181) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.batch.item.data.builder.MongoCursorItemReaderBuilder.build(MongoCursorItemReaderBuilder.java:284) ~[spring-batch-infrastructure-5.2.2.jar:5.2.2]
at com.example.batch.demo.ReportJobConfig.reportReader(ReportJobConfig.java:60) ~[main/:na]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.CGLIB$reportReader$2() ~[main/:na]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$FastClass$$1.invoke() ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:393) ~[spring-context-6.2.7.jar:6.2.7]
at com.example.batch.demo.ReportJobConfig$$SpringCGLIB$$0.reportReader() ~[main/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.2.7.jar:6.2.7]
... 71 common frames omitted

As I understand it, since jsonQuery cannot specify sorting conditions, I think the sorts value only needs to be validated when building through jsonQuery.

Environment
Spring Batch 5.2.2
Spring Boot 3.5.0
Amazon Correto 21.0.3

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions