Skip to content

Commit 244df51

Browse files
committed
Add support for paging queries
1 parent 1c37e4d commit 244df51

File tree

14 files changed

+503
-23
lines changed

14 files changed

+503
-23
lines changed

src/main/java/org/mybatis/dynamic/sql/util/ValueMapping.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ public <R> R accept(UpdateMappingVisitor<R> visitor) {
3838
}
3939

4040
public static <T> ValueMapping<T> of(SqlColumn<T> column, Supplier<T> valueSupplier) {
41-
ValueMapping<T> mapping = new ValueMapping<>(column, valueSupplier);
42-
return mapping;
41+
return new ValueMapping<>(column, valueSupplier);
4342
}
4443
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Copyright 2016-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.util.springbatch;
17+
18+
import org.mybatis.dynamic.sql.select.SelectModel;
19+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
20+
21+
public class SpringBatchCursorReaderSelectModel {
22+
23+
private SelectModel selectModel;
24+
25+
public SpringBatchCursorReaderSelectModel(SelectModel selectModel) {
26+
this.selectModel = selectModel;
27+
}
28+
29+
public SelectStatementProvider render() {
30+
return selectModel.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY);
31+
}
32+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright 2016-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.util.springbatch;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
import org.mybatis.dynamic.sql.select.SelectModel;
22+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
23+
24+
public class SpringBatchPagingReaderSelectModel {
25+
26+
private SelectModel selectModel;
27+
28+
public SpringBatchPagingReaderSelectModel(SelectModel selectModel) {
29+
this.selectModel = selectModel;
30+
}
31+
32+
public SelectStatementProvider render() {
33+
SelectStatementProvider selectStatement =
34+
selectModel.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY);
35+
return new LimitAndOffsetDecorator(selectStatement);
36+
}
37+
38+
public class LimitAndOffsetDecorator implements SelectStatementProvider {
39+
private Map<String, Object> parameters = new HashMap<>();
40+
private String selectStatement;
41+
42+
public LimitAndOffsetDecorator(SelectStatementProvider delegate) {
43+
parameters.putAll(delegate.getParameters());
44+
45+
selectStatement = delegate.getSelectStatement()
46+
+ " LIMIT #{_pagesize} OFFSET #{_skiprows}"; //$NON-NLS-1$
47+
}
48+
49+
@Override
50+
public Map<String, Object> getParameters() {
51+
return parameters;
52+
}
53+
54+
@Override
55+
public String getSelectStatement() {
56+
return selectStatement;
57+
}
58+
}
59+
}

src/main/java/org/mybatis/dynamic/sql/util/springbatch/SpringBatchUtility.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
import java.util.HashMap;
1919
import java.util.Map;
2020

21+
import org.mybatis.dynamic.sql.BasicColumn;
2122
import org.mybatis.dynamic.sql.render.RenderingStrategy;
23+
import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
24+
import org.mybatis.dynamic.sql.select.SelectDSL;
2225
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
2326

2427
public class SpringBatchUtility {
@@ -34,4 +37,29 @@ public static Map<String, Object> toParameterValues(SelectStatementProvider sele
3437
parameterValues.put(PARAMETER_KEY, selectStatement);
3538
return parameterValues;
3639
}
40+
41+
/**
42+
* Select builder that renders in a manner appropriate for the MyBatisPagingItemReader.
43+
*
44+
* <b>Important</b> rendered SQL will contain LIMIT and OFFSET clauses in the SELECT statement. If your database
45+
* (Oracle) does not support LIMIT and OFFSET, the queries will fail.
46+
*
47+
* @param selectList a column list for the SELECT statement
48+
* @return FromGatherer used to continue a SELECT statement
49+
*/
50+
public static QueryExpressionDSL.FromGatherer<SpringBatchPagingReaderSelectModel> selectForPaging(
51+
BasicColumn...selectList) {
52+
return SelectDSL.select(SpringBatchPagingReaderSelectModel::new, selectList);
53+
}
54+
55+
/**
56+
* Select builder that renders in a manner appropriate for the MyBatisCursorItemReader.
57+
*
58+
* @param selectList a column list for the SELECT statement
59+
* @return FromGatherer used to continue a SELECT statement
60+
*/
61+
public static QueryExpressionDSL.FromGatherer<SpringBatchCursorReaderSelectModel> selectForCursor(
62+
BasicColumn...selectList) {
63+
return SelectDSL.select(SpringBatchCursorReaderSelectModel::new, selectList);
64+
}
3765
}

src/site/markdown/docs/springBatch.md

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,69 @@ The solution involves these steps:
1919

2020
MyBatis Dynamic SQL provides utilities for each of these requirements. Each utility uses a shared Map key for consistency.
2121

22-
### Specialized Rendering
22+
## Spring Batch Item Readers
2323

24-
MyBatis Dynamic SQL provides a specialized rendering strategy for queries used with the MyBatis Spring `ItemReader` implementations. Queries should be rendered as follows:
24+
MyBatis Spring support supplies two implementations of the `ItemReader` interface:
25+
26+
1. `org.mybatis.spring.batch.MyBatisCursorItemReader` - for queries that can be efficiently processed through a single select statement and a cursor
27+
1. `org.mybatis.spring.batch.MyBatisPagingItemReader` - for queries that should be processed as a series of paged selects. Note that MyBatis does not provide any native support for paged queries - it is up to the user to write SQL for paging. The `MyBatisPagingItemWriter` simply makes properties available that specify which page should be read currently.
28+
29+
MyBatis Dynamic SQL supplies specialized select statements that will render properly for the different implementations of `ItemReader`:
30+
31+
1. `SpringBatchUtility.selectForCursor(...)` will create a select statement that is appropriate for the `MyBatisCursorItemReader` - a single select statement that will be read with a cursor
32+
1. `SpringBatchUtility.selectForPaging(...)` will create a select statement that is appropriate for the `MyBatisPagingItemReader` - a select statement that will be called multiple times - one for each page as configured on the batch job.
33+
34+
**Very Important:** The paging implementation will only work for databases that support limit and offset in select statements. Fortunately, most databases do support this - with the notable exception of Oracle.
35+
36+
37+
### Rendering for Cursor
38+
39+
Queries intended for the `MyBatisCursorItemReader` should be rendered as follows:
40+
41+
```java
42+
SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns())
43+
.from(person)
44+
.where(lastName, isEqualTo("flintstone"))
45+
.build()
46+
.render(); // renders for MyBatisCursorItemReader
47+
```
48+
49+
### Rendering for Paging
50+
51+
Queries intended for the `MyBatisPagingItemReader` should be rendered as follows:
2552

2653
```java
27-
SelectStatementProvider selectStatement = SelectDSL.select(person.allColumns())
54+
SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns())
2855
.from(person)
2956
.where(lastName, isEqualTo("flintstone"))
3057
.build()
31-
.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY); // render for Spring Batch
58+
.render(); // renders for MyBatisPagingItemReader
3259
```
3360

34-
### Creating the Parameter Map
61+
## Creating the Parameter Map
62+
63+
The `SpringBatchUtility` provides a method to create the parameter values Map needed by the MyBatis Spring `ItemReader` implementations. It can be used as follows:
3564

36-
The `SpringBatchUtility` provides a method to create the parameter values Map needed by the MyBatis Spring `ItemReader`. It can be used as follows:
65+
For cursor based queries...
3766

3867
```java
3968
MyBatisCursorItemReader<Person> reader = new MyBatisCursorItemReader<>();
4069
reader.setQueryId(PersonMapper.class.getName() + ".selectMany");
4170
reader.setSqlSessionFactory(sqlSessionFactory);
4271
reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement)); // create parameter map
4372
```
73+
For paging based queries...
74+
75+
```java
76+
MyBatisPagingItemReader<Person> reader = new MyBatisPagingItemReader<>();
77+
reader.setQueryId(PersonMapper.class.getName() + ".selectMany");
78+
reader.setSqlSessionFactory(sqlSessionFactory);
79+
reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement));
80+
reader.setPageSize(7);
81+
```
82+
4483

45-
### Specialized @SelectProvider Adapter
84+
## Specialized @SelectProvider Adapter
4685

4786
MyBatis mapper methods should be configured to use the specialized `@SelectProvider` adapter as follows:
4887

@@ -58,4 +97,4 @@ MyBatis mapper methods should be configured to use the specialized `@SelectProvi
5897

5998
## Complete Example
6099

61-
The unit tests for MyBatis Dynamic SQL include a complete example of using MyBatis Spring Batch support using both a reader and writer. You can see the full example here: [https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch](https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch)
100+
The unit tests for MyBatis Dynamic SQL include a complete examples of using MyBatis Spring Batch support using the MyBatis supplied reader as well as both types of MyBatis supplied writers. You can see the full example here: [https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch](https://github.com/mybatis/mybatis-dynamic-sql/tree/master/src/test/java/examples/springbatch)

src/test/java/examples/springbatch/BatchConfiguration.java renamed to src/test/java/examples/springbatch/CursorReaderBatchConfiguration.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import javax.sql.DataSource;
2222

2323
import org.apache.ibatis.session.SqlSessionFactory;
24-
import org.mybatis.dynamic.sql.select.SelectDSL;
2524
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
2625
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
2726
import org.mybatis.dynamic.sql.util.springbatch.SpringBatchUtility;
@@ -49,9 +48,9 @@
4948

5049
@EnableBatchProcessing
5150
@Configuration
52-
@MapperScan("examples.springbatch")
5351
@ComponentScan("examples.springbatch")
54-
public class BatchConfiguration {
52+
@MapperScan("examples.springbatch")
53+
public class CursorReaderBatchConfiguration {
5554

5655
@Autowired
5756
private JobBuilderFactory jobBuilderFactory;
@@ -82,11 +81,11 @@ public JobLauncherTestUtils jobLauncherTestUtils() {
8281

8382
@Bean
8483
public MyBatisCursorItemReader<Person> reader(SqlSessionFactory sqlSessionFactory) {
85-
SelectStatementProvider selectStatement = SelectDSL.select(person.allColumns())
84+
SelectStatementProvider selectStatement = SpringBatchUtility.selectForCursor(person.allColumns())
8685
.from(person)
8786
.where(lastName, isEqualTo("flintstone"))
8887
.build()
89-
.render(SpringBatchUtility.SPRING_BATCH_READER_RENDERING_STRATEGY);
88+
.render();
9089

9190
MyBatisCursorItemReader<Person> reader = new MyBatisCursorItemReader<>();
9291
reader.setQueryId(PersonMapper.class.getName() + ".selectMany");
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Copyright 2016-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package examples.springbatch;
17+
18+
import static examples.springbatch.PersonDynamicSqlSupport.*;
19+
import static org.mybatis.dynamic.sql.SqlBuilder.*;
20+
21+
import javax.sql.DataSource;
22+
23+
import org.apache.ibatis.session.SqlSessionFactory;
24+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
25+
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
26+
import org.mybatis.dynamic.sql.util.springbatch.SpringBatchUtility;
27+
import org.mybatis.spring.SqlSessionFactoryBean;
28+
import org.mybatis.spring.annotation.MapperScan;
29+
import org.mybatis.spring.batch.MyBatisBatchItemWriter;
30+
import org.mybatis.spring.batch.MyBatisPagingItemReader;
31+
import org.springframework.batch.core.Job;
32+
import org.springframework.batch.core.Step;
33+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
34+
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
35+
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
36+
import org.springframework.batch.core.launch.support.RunIdIncrementer;
37+
import org.springframework.batch.item.ItemProcessor;
38+
import org.springframework.batch.item.ItemReader;
39+
import org.springframework.batch.item.ItemWriter;
40+
import org.springframework.batch.test.JobLauncherTestUtils;
41+
import org.springframework.beans.factory.annotation.Autowired;
42+
import org.springframework.context.annotation.Bean;
43+
import org.springframework.context.annotation.ComponentScan;
44+
import org.springframework.context.annotation.Configuration;
45+
import org.springframework.core.convert.converter.Converter;
46+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
47+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
48+
49+
@EnableBatchProcessing
50+
@Configuration
51+
@ComponentScan("examples.springbatch")
52+
@MapperScan("examples.springbatch")
53+
public class PagingReaderBatchConfiguration {
54+
55+
@Autowired
56+
private JobBuilderFactory jobBuilderFactory;
57+
58+
@Autowired
59+
private StepBuilderFactory stepBuilderFactory;
60+
61+
@Bean
62+
public DataSource dataSource() {
63+
return new EmbeddedDatabaseBuilder()
64+
.setType(EmbeddedDatabaseType.HSQL)
65+
.addScript("classpath:/examples/springbatch/schema.sql")
66+
.addScript("classpath:/examples/springbatch/data.sql")
67+
.build();
68+
}
69+
70+
@Bean
71+
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
72+
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
73+
bean.setDataSource(dataSource);
74+
return bean;
75+
}
76+
77+
@Bean
78+
public JobLauncherTestUtils jobLauncherTestUtils() {
79+
return new JobLauncherTestUtils();
80+
}
81+
82+
@Bean
83+
public MyBatisPagingItemReader<Person> reader(SqlSessionFactory sqlSessionFactory) {
84+
SelectStatementProvider selectStatement = SpringBatchUtility.selectForPaging(person.allColumns())
85+
.from(person)
86+
.where(forPagingTest, isEqualTo(true))
87+
.orderBy(id)
88+
.build()
89+
.render();
90+
91+
MyBatisPagingItemReader<Person> reader = new MyBatisPagingItemReader<>();
92+
reader.setQueryId(PersonMapper.class.getName() + ".selectMany");
93+
reader.setSqlSessionFactory(sqlSessionFactory);
94+
reader.setParameterValues(SpringBatchUtility.toParameterValues(selectStatement));
95+
reader.setPageSize(7);
96+
return reader;
97+
}
98+
99+
@Bean
100+
public MyBatisBatchItemWriter<Person> writer(SqlSessionFactory sqlSessionFactory,
101+
Converter<Person, UpdateStatementProvider> convertor) {
102+
MyBatisBatchItemWriter<Person> writer = new MyBatisBatchItemWriter<>();
103+
writer.setSqlSessionFactory(sqlSessionFactory);
104+
writer.setItemToParameterConverter(convertor);
105+
writer.setStatementId(PersonMapper.class.getName() + ".update");
106+
return writer;
107+
}
108+
109+
@Bean
110+
public Step step1(ItemReader<Person> reader, ItemProcessor<Person, Person> processor, ItemWriter<Person> writer) {
111+
return stepBuilderFactory.get("step1")
112+
.<Person, Person>chunk(7)
113+
.reader(reader)
114+
.processor(processor)
115+
.writer(writer)
116+
.build();
117+
}
118+
119+
@Bean
120+
public Job upperCaseLastName(Step step1) {
121+
return jobBuilderFactory.get("upperCaseLastName")
122+
.incrementer(new RunIdIncrementer())
123+
.flow(step1)
124+
.end()
125+
.build();
126+
}
127+
}

0 commit comments

Comments
 (0)