From 40aa0bcbfeb70c4a2b644ee2b9a851df863d19df Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 20 Oct 2025 15:53:57 -0400 Subject: [PATCH] Add an example of converting a select statement to a count statement --- .../examples/simple/ReusableWhereTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/test/java/examples/simple/ReusableWhereTest.java b/src/test/java/examples/simple/ReusableWhereTest.java index f3b6d1d4e..bde7ef1f3 100644 --- a/src/test/java/examples/simple/ReusableWhereTest.java +++ b/src/test/java/examples/simple/ReusableWhereTest.java @@ -17,9 +17,13 @@ import static examples.simple.PersonDynamicSqlSupport.id; import static examples.simple.PersonDynamicSqlSupport.occupation; +import static examples.simple.PersonDynamicSqlSupport.person; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo; +import static org.mybatis.dynamic.sql.SqlBuilder.isLessThan; import static org.mybatis.dynamic.sql.SqlBuilder.isNull; +import static org.mybatis.dynamic.sql.SqlBuilder.select; import static org.mybatis.dynamic.sql.SqlBuilder.where; import java.io.InputStream; @@ -38,6 +42,12 @@ import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.render.RenderingStrategies; +import org.mybatis.dynamic.sql.select.QueryExpressionModel; +import org.mybatis.dynamic.sql.select.SelectModel; +import org.mybatis.dynamic.sql.select.SubQuery; +import org.mybatis.dynamic.sql.select.aggregate.CountAll; +import org.mybatis.dynamic.sql.select.render.SelectStatementProvider; import org.mybatis.dynamic.sql.where.WhereApplier; class ReusableWhereTest { @@ -114,5 +124,64 @@ void testUpdate() { } } + @Test + void testTransformToCount() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonMapper mapper = session.getMapper(PersonMapper.class); + + SelectModel selectModel = select(PersonMapper.selectList) + .from(person) + .where(id, isLessThan(5)) + .limit(2) + .build(); + + SelectStatementProvider selectStatement = selectModel.render(RenderingStrategies.MYBATIS3); + + assertThat(selectStatement.getSelectStatement()).isEqualTo( + "select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id from Person where id < #{parameters.p1,jdbcType=INTEGER} limit #{parameters.p2}"); + assertThat(selectStatement.getParameters()).containsOnly(entry("p1", 5), entry("p2", 2L)); + + SelectModel countModel = toCount(selectModel); + SelectStatementProvider countStatement = countModel.render(RenderingStrategies.MYBATIS3); + + assertThat(countStatement.getSelectStatement()).isEqualTo( + "select count(*) from (select id as A_ID, first_name, last_name, birth_date, employed, occupation, address_id from Person where id < #{parameters.p1,jdbcType=INTEGER})"); + assertThat(countStatement.getParameters()).containsOnly(entry("p1", 5)); + + long count = mapper.count(countStatement); + + assertThat(count).isEqualTo(4); + } + + } + private final WhereApplier commonWhere = where(id, isEqualTo(1)).or(occupation, isNull()).toWhereApplier(); + + /** + * This function transforms a select statement into a count statement by wrapping the select statement into + * a subquery. This can be used to create a single select statement and use it for both selects and counts + * in a paging scenario. This is more appropriate than a reusable where clause if the query is complex. For simple + * queries, a reusable where clause is best. + * + *

This function will strip any paging configuration, waits, order bys, etc. from the top level query. This + * will allow usage of a paging query for selects, and the transformed query for a count of all rows. + * + * @param selectModel the select model to transform + * @return a new select model that is "select count(*) from (subquery)" where subquery is the input select statement + */ + static SelectModel toCount(SelectModel selectModel) { + // remove any paging configuration, order by, wait clause, etc. from the incoming select model + SelectModel strippedSelectModel = SelectModel.withQueryExpressions(selectModel.queryExpressions().toList()) + .withStatementConfiguration(selectModel.statementConfiguration()) + .build(); + + QueryExpressionModel model = QueryExpressionModel + .withSelectList(List.of(new CountAll())) + .withTable(new SubQuery.Builder().withSelectModel(strippedSelectModel).build()) + .build(); + + return SelectModel.withQueryExpressions(List.of(model)) + .withStatementConfiguration(selectModel.statementConfiguration()) + .build(); + } }