diff --git a/docs/zh/others/apt.md b/docs/zh/others/apt.md index 4d796f46..70b8a58d 100644 --- a/docs/zh/others/apt.md +++ b/docs/zh/others/apt.md @@ -132,14 +132,48 @@ processor.mappersPackage = com.your-package processor.baseMapperClass=com.domain.mapper.MyBaseMapper ``` +## 实体类不在一个包中 +有时候可能会遇到实体类不在同一个包中的情况,例如: + +```text +com.example.entityPackage1 + └─ Entity1 +com.example.entityPackage2 + └─ Entity2 +``` + +此时的辅助类会生成在对应的包下,例如: + +```text +com.example.entityPackage1.table + └─ Entity1TableDef +com.example.entityPackage2.table + └─ Entity2TableDef +``` + +但是,如果您设置了 `processor.allInTables=true` 的话,`Tables` 文件将会生成在最后一个包中,例如: + +```text +com.example.entityPackage2.table + └─ Tables +``` + +所以,如果您的实体类在多个包中,又指定了 `processor.allInTables=true` 选项,推荐设置 `Tables` 文件的位置,例如: + +```properties +processor.allInTables=true +processor.tablesPackage=com.example.entity.table +``` ## 和 Lombok、Mapstruct 整合 -在很多项目中,用到了 Lombok 帮我们减少代码编写,同时用到 Mapstruct 进行 bean 转换。使用到 Lombok 和 Mapstruct 时,其要求我们再 pom.xml 添加 `annotationProcessorPaths` 配置, +在很多项目中,用到了 Lombok 帮我们减少代码编写,同时用到 Mapstruct 进行 bean 转换。使用到 Lombok 和 Mapstruct 时,其要求我们再 +pom.xml 添加 `annotationProcessorPaths` 配置, 此时,我们也需要把 MyBatis-Flex 的 annotation 添加到 `annotationProcessorPaths` 配置里去,如下图所示: ```xml 24,25,26,27,28 + org.apache.maven.plugins maven-compiler-plugin diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java index b7dec258..fdda9737 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/BaseMapper.java @@ -20,7 +20,10 @@ import com.mybatisflex.core.field.FieldQueryBuilder; import com.mybatisflex.core.mybatis.MappedStatementTypes; import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.provider.EntitySqlProvider; -import com.mybatisflex.core.query.*; +import com.mybatisflex.core.query.CPI; +import com.mybatisflex.core.query.QueryColumn; +import com.mybatisflex.core.query.QueryCondition; +import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.table.TableInfo; import com.mybatisflex.core.table.TableInfoFactory; import com.mybatisflex.core.util.*; @@ -667,47 +670,43 @@ public interface BaseMapper { default Page paginateAs(Page page, QueryWrapper queryWrapper, Class asType, Consumer>... consumers) { - List selectColumns = CPI.getSelectColumns(queryWrapper); - List orderBys = CPI.getOrderBys(queryWrapper); - List joins = CPI.getJoins(queryWrapper); - // 只有 totalRow 小于 0 的时候才会去查询总量 - // 这样方便用户做总数缓存,而非每次都要去查询总量 - // 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的 - if (page.getTotalRow() < 0) { - QueryWrapper countQueryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper); - long count = selectCountByQuery(countQueryWrapper); - page.setTotalRow(count); - } + try { + // 只有 totalRow 小于 0 的时候才会去查询总量 + // 这样方便用户做总数缓存,而非每次都要去查询总量 + // 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的 + if (page.getTotalRow() < 0) { + QueryWrapper countQueryWrapper; + if (page.isOptimizeCountSql()) { + countQueryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper); + } else { + countQueryWrapper = MapperUtil.rawCountQueryWrapper(queryWrapper); + } + page.setTotalRow(selectCountByQuery(countQueryWrapper)); + } + + if (page.isEmpty()) { + return page; + } + + queryWrapper.limit(page.getOffset(), page.getPageSize()); + + List records; + if (asType != null) { + records = selectListByQueryAs(queryWrapper, asType); + } else { + records = (List) selectListByQuery(queryWrapper); + } + MapperUtil.queryFields(this, records, consumers); + page.setRecords(records); - if (page.isEmpty()) { return page; + + } finally { + // 将之前设置的 limit 清除掉 + // 保险起见把重置代码放到 finally 代码块中 + CPI.setLimitRows(queryWrapper, null); + CPI.setLimitOffset(queryWrapper, null); } - - //重置 selectColumns - CPI.setSelectColumns(queryWrapper, selectColumns); - //重置 orderBys - CPI.setOrderBys(queryWrapper, orderBys); - //重置 join - CPI.setJoins(queryWrapper, joins); - - int offset = page.getPageSize() * (page.getPageNumber() - 1); - queryWrapper.limit(offset, page.getPageSize()); - - if (asType != null) { - List records = selectListByQueryAs(queryWrapper, asType); - MapperUtil.queryFields(this, records, consumers); - page.setRecords(records); - } else { - List records = (List) selectListByQuery(queryWrapper); - MapperUtil.queryFields(this, records, consumers); - page.setRecords(records); - } - - // 将之前设置的 limit 清除掉 - CPI.setLimitRows(queryWrapper, null); - CPI.setLimitOffset(queryWrapper, null); - - return page; } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/IPage.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/IPage.java new file mode 100644 index 00000000..50e9a207 --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/IPage.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mybatisflex.core.paginate; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页接口。 + * + * @param 数据类型 + * @author 王帅 + * @since 2023-06-18 + */ +public interface IPage extends Serializable { + + /** + * 获取当前页码。 + * + * @return 页码 + */ + int getPageNumber(); + + /** + * 设置当前页码。 + * + * @param pageNumber 页码 + */ + void setPageNumber(int pageNumber); + + /** + * 获取当前每页数据数量。 + * + * @return 每页数据数量 + */ + int getPageSize(); + + /** + * 设置当前每页数据数量。 + * + * @param pageSize 每页数据数量 + */ + void setPageSize(int pageSize); + + /** + * 获取数据总数。 + * + * @return 数据总数 + */ + long getTotalRow(); + + /** + * 设置数据总数。 + * + * @param totalRow 数据总数 + */ + void setTotalRow(long totalRow); + + /** + * 获取当前页的数据。 + * + * @return 当前页的数据 + */ + List getRecords(); + + /** + * 设置当前页的数据。 + * + * @param records 当前页的数据 + */ + void setRecords(List records); + + /** + * 获取当前分页偏移量。 + * + * @return 偏移量 + */ + default int getOffset() { + return getPageSize() * (getPageNumber() - 1); + } + + /** + * 是否自动优化 COUNT 查询语句(默认优化)。 + * + * @return {@code true} 优化,{@code false} 不优化 + */ + default boolean isOptimizeCountSql() { + return true; + } + + /** + * 设置是否自动优化 COUNT 查询语句。 + * + * @param optimizeCountSql 是否优化 + */ + default void setOptimizeCountSql(boolean optimizeCountSql) { + // 默认总是优化 + } + + /** + * 获取总页数。 + * + * @return 总页数 + */ + default long getTotalPage() { + // 实时计算总页数 + int pageSize = getPageSize(); + if (pageSize == 0) { + return 0L; + } + long totalRow = getTotalRow(); + long totalPage = totalRow / pageSize; + if (totalRow % pageSize != 0) { + totalPage++; + } + return totalPage; + } + + /** + * 设置总页数。 + * + * @param totalPage 总页数 + */ + default void setTotalPage(long totalPage) { + // 总页数是实时计算的,所以这里设置了也没用。 + } + + /** + * 是否存在上一页。 + * + * @return {@code true} 存在上一页,{@code false} 不存在上一页 + */ + default boolean hasPrevious() { + return getPageNumber() > 1; + } + + /** + * 是否存在下一页。 + * + * @return {@code true} 存在下一页,{@code false} 不存在下一页 + */ + default boolean hasNext() { + return getPageNumber() < getTotalPage(); + } + + /** + * 当前页是否为空。 + * + * @return {@code true} 空页,{@code false} 非空页 + */ + default boolean isEmpty() { + return getTotalRow() == 0 || getPageNumber() > getTotalPage(); + } + +} \ No newline at end of file diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/Page.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/Page.java index 8b4e75d2..4ad603c8 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/Page.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/paginate/Page.java @@ -15,13 +15,12 @@ */ package com.mybatisflex.core.paginate; -import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; -public class Page implements Serializable { +public class Page implements IPage { private static final long serialVersionUID = 1L; public static final int INIT_VALUE = -1; @@ -32,6 +31,8 @@ public class Page implements Serializable { private long totalPage = INIT_VALUE; private long totalRow = INIT_VALUE; + private boolean optimizeCountSql = true; + public static Page of(int pageNumber, int pageSize) { return new Page<>(pageNumber, pageSize); } @@ -64,10 +65,22 @@ public class Page implements Serializable { } + @Override + public boolean isOptimizeCountSql() { + return optimizeCountSql; + } + + @Override + public void setOptimizeCountSql(boolean optimizeCountSql) { + this.optimizeCountSql = optimizeCountSql; + } + + @Override public List getRecords() { return records; } + @Override public void setRecords(List records) { if (records == null) { records = Collections.emptyList(); @@ -75,10 +88,12 @@ public class Page implements Serializable { this.records = records; } + @Override public int getPageNumber() { return pageNumber; } + @Override public void setPageNumber(int pageNumber) { if (pageNumber < 1) { throw new IllegalArgumentException("pageNumber must greater than or equal 1,current value is: " + pageNumber); @@ -87,10 +102,12 @@ public class Page implements Serializable { } + @Override public int getPageSize() { return pageSize; } + @Override public void setPageSize(int pageSize) { if (pageSize < 0) { throw new IllegalArgumentException("pageSize must greater than or equal 0,current value is: " + pageSize); @@ -99,18 +116,22 @@ public class Page implements Serializable { this.calcTotalPage(); } + @Override public long getTotalPage() { return totalPage; } + @Override public void setTotalPage(long totalPage) { this.totalPage = totalPage; } + @Override public long getTotalRow() { return totalRow; } + @Override public void setTotalRow(long totalRow) { this.totalRow = totalRow; this.calcTotalPage(); @@ -127,10 +148,12 @@ public class Page implements Serializable { } } + @Override public boolean isEmpty() { return getTotalRow() == 0 || getPageNumber() > getTotalPage(); } + @Override public boolean hasNext() { return getTotalPage() != 0 && getPageNumber() < getTotalPage(); } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowMapper.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowMapper.java index 1c79619f..9467ca92 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowMapper.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowMapper.java @@ -19,7 +19,9 @@ import com.mybatisflex.core.FlexConsts; import com.mybatisflex.core.exception.FlexExceptions; import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.provider.RowSqlProvider; -import com.mybatisflex.core.query.*; +import com.mybatisflex.core.query.CPI; +import com.mybatisflex.core.query.QueryColumn; +import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.util.CollectionUtil; import com.mybatisflex.core.util.MapperUtil; import com.mybatisflex.core.util.StringUtil; @@ -431,47 +433,39 @@ public interface RowMapper { * @return */ default Page paginate(String schema, String tableName, Page page, QueryWrapper queryWrapper) { + try { + CPI.setFromIfNecessary(queryWrapper, schema, tableName); - CPI.setFromIfNecessary(queryWrapper, schema, tableName); + // 只有 totalRow 小于 0 的时候才会去查询总量 + // 这样方便用户做总数缓存,而非每次都要去查询总量 + // 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的 + if (page.getTotalRow() < 0) { + QueryWrapper countQueryWrapper; + if (page.isOptimizeCountSql()) { + countQueryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper); + } else { + countQueryWrapper = MapperUtil.rawCountQueryWrapper(queryWrapper); + } + page.setTotalRow(selectCountByQuery(schema, tableName, countQueryWrapper)); + } - List selectColumns = CPI.getSelectColumns(queryWrapper); + if (page.isEmpty()) { + return page; + } - List orderBys = CPI.getOrderBys(queryWrapper); + queryWrapper.limit(page.getOffset(), page.getPageSize()); - List joins = CPI.getJoins(queryWrapper); + page.setRecords(selectListByQuery(schema, tableName, queryWrapper)); - // 只有 totalRow 小于 0 的时候才会去查询总量 - // 这样方便用户做总数缓存,而非每次都要去查询总量 - // 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的 - if (page.getTotalRow() < 0) { - QueryWrapper countQueryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper); - long count = selectCountByQuery(schema, tableName, countQueryWrapper); - page.setTotalRow(count); - } - - if (page.isEmpty()) { return page; + + } finally { + // 将之前设置的 limit 清除掉 + // 保险起见把重置代码放到 finally 代码块中 + CPI.setLimitRows(queryWrapper, null); + CPI.setLimitOffset(queryWrapper, null); } - //重置 selectColumns - CPI.setSelectColumns(queryWrapper, selectColumns); - //重置 orderBys - CPI.setOrderBys(queryWrapper, orderBys); - //重置 join - CPI.setJoins(queryWrapper, joins); - - int offset = page.getPageSize() * (page.getPageNumber() - 1); - queryWrapper.limit(offset, page.getPageSize()); - - List records = selectListByQuery(schema, tableName, queryWrapper); - page.setRecords(records); - - // 将之前设置的 limit 清除掉 - CPI.setLimitRows(queryWrapper, null); - CPI.setLimitOffset(queryWrapper, null); - - return page; - } } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/MapperUtil.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/MapperUtil.java index d8e2c1ea..3f8f40b8 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/MapperUtil.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/util/MapperUtil.java @@ -42,7 +42,7 @@ public class MapperUtil { * *

      * {@code
-     * SELECT COUNT(*) AS `total` FROM ( ...用户构建的 SQL 语句... );
+     * SELECT COUNT(*) AS `total` FROM ( ...用户构建的 SQL 语句... ) AS `t`;
      * }
      * 
* @@ -58,22 +58,25 @@ public class MapperUtil { * 优化 COUNT 查询语句。 */ public static QueryWrapper optimizeCountQueryWrapper(QueryWrapper queryWrapper) { - List selectColumns = CPI.getSelectColumns(queryWrapper); - List groupByColumns = CPI.getGroupByColumns(queryWrapper); + // 对克隆对象进行操作,不影响原来的 QueryWrapper 对象 + QueryWrapper clone = queryWrapper.clone(); + // 将最后面的 order by 移除掉 + CPI.setOrderBys(clone, null); + // 获取查询列和分组列,用于判断是否进行优化 + List selectColumns = CPI.getSelectColumns(clone); + List groupByColumns = CPI.getGroupByColumns(clone); // 如果有 distinct 语句或者 group by 语句则不优化 // 这种一旦优化了就会造成 count 语句查询出来的值不对 if (hasDistinct(selectColumns) || hasGroupBy(groupByColumns)) { - return rawCountQueryWrapper(queryWrapper); + return rawCountQueryWrapper(clone); } // 判断能不能清除 join 语句 - if (canClearJoins(queryWrapper)) { - CPI.setJoins(queryWrapper, null); + if (canClearJoins(clone)) { + CPI.setJoins(clone, null); } - // 最后将最后面的 order by 移除掉 - // 将 select 里面的列换成 COUNT(*) AS `total` 就好了 - CPI.setOrderBys(queryWrapper, null); - CPI.setSelectColumns(queryWrapper, Collections.singletonList(count().as("total"))); - return queryWrapper; + // 将 select 里面的列换成 COUNT(*) AS `total` + CPI.setSelectColumns(clone, Collections.singletonList(count().as("total"))); + return clone; } private static boolean hasDistinct(List selectColumns) { diff --git a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/ConditionalOnMybatisFlexDatasource.java b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/ConditionalOnMybatisFlexDatasource.java index 8066f807..604fcb4a 100644 --- a/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/ConditionalOnMybatisFlexDatasource.java +++ b/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/ConditionalOnMybatisFlexDatasource.java @@ -19,6 +19,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.env.*; import org.springframework.core.type.AnnotatedTypeMetadata; @@ -34,6 +36,7 @@ import java.util.Iterator; @Conditional(ConditionalOnMybatisFlexDatasource.OnMybatisFlexDataSourceCondition.class) public @interface ConditionalOnMybatisFlexDatasource { + @Order(Ordered.HIGHEST_PRECEDENCE + 40) class OnMybatisFlexDataSourceCondition extends SpringBootCondition { @Override @@ -44,8 +47,8 @@ public @interface ConditionalOnMybatisFlexDatasource { Iterator> it = propertySources.stream().iterator(); while (it.hasNext()) { PropertySource ps = it.next(); - if (ps instanceof MapPropertySource) { - for (String propertyName : ((MapPropertySource) ps).getSource().keySet()) { + if (ps instanceof EnumerablePropertySource) { + for (String propertyName : ((EnumerablePropertySource) ps).getPropertyNames()) { if (propertyName.startsWith("mybatis-flex.datasource.")) { return ConditionOutcome.match(); } diff --git a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/UserMapperTest.java b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/UserMapperTest.java index e03037e0..9e3079f1 100644 --- a/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/UserMapperTest.java +++ b/mybatis-flex-test/mybatis-flex-spring-boot-test/src/test/java/com/mybatisflex/test/mapper/UserMapperTest.java @@ -143,10 +143,11 @@ class UserMapperTest { .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID)) .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID)); System.err.println(queryWrapper.toSQL()); - Page page; + Page page = Page.of(1, 1); + page.setOptimizeCountSql(false); int pageNumber = 0; do { - page = Page.of(++pageNumber, 1); + page.setPageNumber(page.getPageNumber() + 1); page = userMapper.paginateAs(page, queryWrapper, UserVO.class); System.err.println(page); } while (page.hasNext());