Skip to content

Commit 55f0d28

Browse files
committed
Add a test showing how to write paginated queries
1 parent 2c4c5f0 commit 55f0d28

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Copyright 2016-2018 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.paging;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.Objects;
21+
import java.util.function.Function;
22+
23+
import org.mybatis.dynamic.sql.render.RenderingStrategy;
24+
import org.mybatis.dynamic.sql.select.SelectModel;
25+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
26+
27+
/**
28+
* This adapter modifies the generated SQL by adding a LIMIT and OFFSET clause at the end
29+
* of the generated SQL. This can be used to create a paginated query.
30+
*
31+
* LIMIT and OFFSET has limited support in relational databases, so this cannot be considered
32+
* a general solution for all paginated queries (and that is why this adapter lives only in the
33+
* test source tree and is not packaged with the core library code).
34+
*
35+
* I believe it works in MySQL, HSQLDB, and Postgres.
36+
*
37+
* @author Jeff Butler
38+
*/
39+
public class LimitAndOffsetAdapter<R> {
40+
private SelectModel selectModel;
41+
private Function<SelectStatementProvider, R> mapperMethod;
42+
private int limit;
43+
private int offset;
44+
45+
private LimitAndOffsetAdapter(SelectModel selectModel, Function<SelectStatementProvider, R> mapperMethod,
46+
int limit, int offset) {
47+
this.selectModel = Objects.requireNonNull(selectModel);
48+
this.mapperMethod = Objects.requireNonNull(mapperMethod);
49+
this.limit = limit;
50+
this.offset = offset;
51+
}
52+
53+
public R execute() {
54+
return mapperMethod.apply(selectStatement());
55+
}
56+
57+
private SelectStatementProvider selectStatement() {
58+
return new LimitAndOffsetDecorator(
59+
selectModel.render(RenderingStrategy.MYBATIS3));
60+
}
61+
62+
public static <R> LimitAndOffsetAdapter<R> of(SelectModel selectModel,
63+
Function<SelectStatementProvider, R> mapperMethod, int limit, int offset) {
64+
return new LimitAndOffsetAdapter<>(selectModel, mapperMethod, limit, offset);
65+
}
66+
67+
public class LimitAndOffsetDecorator implements SelectStatementProvider {
68+
private Map<String, Object> parameters = new HashMap<>();
69+
private String selectStatement;
70+
71+
public LimitAndOffsetDecorator(SelectStatementProvider delegate) {
72+
parameters.putAll(delegate.getParameters());
73+
parameters.put("limit", limit);
74+
parameters.put("offset", offset);
75+
76+
selectStatement = delegate.getSelectStatement() +
77+
" LIMIT #{parameters.limit} OFFSET #{parameters.offset}";
78+
}
79+
80+
@Override
81+
public Map<String, Object> getParameters() {
82+
return parameters;
83+
}
84+
85+
@Override
86+
public String getSelectStatement() {
87+
return selectStatement;
88+
}
89+
}
90+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Copyright 2016-2018 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.paging;
17+
18+
import static examples.animal.data.AnimalDataDynamicSqlSupport.*;
19+
20+
import java.util.List;
21+
22+
import org.apache.ibatis.annotations.Result;
23+
import org.apache.ibatis.annotations.Results;
24+
import org.apache.ibatis.annotations.SelectProvider;
25+
import org.mybatis.dynamic.sql.select.QueryExpressionDSL;
26+
import org.mybatis.dynamic.sql.select.SelectDSL;
27+
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
28+
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
29+
30+
import examples.animal.data.AnimalData;
31+
32+
public interface LimitAndOffsetMapper {
33+
34+
@SelectProvider(type=SqlProviderAdapter.class, method="select")
35+
@Results(id="AnimalDataResult", value={
36+
@Result(column="id", property="id", id=true),
37+
@Result(column="animal_name", property="animalName"),
38+
@Result(column="brain_weight", property="brainWeight"),
39+
@Result(column="body_weight", property="bodyWeight")
40+
})
41+
List<AnimalData> selectMany(SelectStatementProvider selectStatement);
42+
43+
default QueryExpressionDSL<LimitAndOffsetAdapter<List<AnimalData>>> selectByExampleWithLimitAndOffset(int limit, int offset) {
44+
return SelectDSL.select(selectModel -> LimitAndOffsetAdapter.of(selectModel, this::selectMany, limit, offset),
45+
id, animalName, brainWeight, bodyWeight)
46+
.from(animalData);
47+
}
48+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Copyright 2016-2018 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.paging;
17+
18+
import static examples.animal.data.AnimalDataDynamicSqlSupport.*;
19+
import static org.assertj.core.api.Assertions.*;
20+
21+
import java.io.InputStream;
22+
import java.io.InputStreamReader;
23+
import java.sql.Connection;
24+
import java.sql.DriverManager;
25+
import java.util.List;
26+
27+
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
28+
import org.apache.ibatis.jdbc.ScriptRunner;
29+
import org.apache.ibatis.mapping.Environment;
30+
import org.apache.ibatis.session.Configuration;
31+
import org.apache.ibatis.session.SqlSession;
32+
import org.apache.ibatis.session.SqlSessionFactory;
33+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
34+
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
35+
import org.junit.jupiter.api.BeforeEach;
36+
import org.junit.jupiter.api.Test;
37+
import org.junit.platform.runner.JUnitPlatform;
38+
import org.junit.runner.RunWith;
39+
40+
import examples.animal.data.AnimalData;
41+
42+
@RunWith(JUnitPlatform.class)
43+
public class LimitAndOffsetTest {
44+
45+
private static final String JDBC_URL = "jdbc:hsqldb:mem:aname";
46+
private static final String JDBC_DRIVER = "org.hsqldb.jdbcDriver";
47+
48+
private SqlSessionFactory sqlSessionFactory;
49+
50+
@BeforeEach
51+
public void setup() throws Exception {
52+
Class.forName(JDBC_DRIVER);
53+
InputStream is = getClass().getResourceAsStream("/examples/animal/data/CreateAnimalData.sql");
54+
try (Connection connection = DriverManager.getConnection(JDBC_URL, "sa", "")) {
55+
ScriptRunner sr = new ScriptRunner(connection);
56+
sr.setLogWriter(null);
57+
sr.runScript(new InputStreamReader(is));
58+
}
59+
60+
UnpooledDataSource ds = new UnpooledDataSource(JDBC_DRIVER, JDBC_URL, "sa", "");
61+
Environment environment = new Environment("test", new JdbcTransactionFactory(), ds);
62+
Configuration config = new Configuration(environment);
63+
config.addMapper(LimitAndOffsetMapper.class);
64+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);
65+
}
66+
67+
@Test
68+
public void testLimitandOffset() {
69+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
70+
LimitAndOffsetMapper mapper = sqlSession.getMapper(LimitAndOffsetMapper.class);
71+
72+
List<AnimalData> rows = mapper.selectByExampleWithLimitAndOffset(5, 3)
73+
.orderBy(id)
74+
.build()
75+
.execute();
76+
77+
assertThat(rows.size()).isEqualTo(5);
78+
assertThat(rows.get(0).getId()).isEqualTo(4);
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)