!58 Knowing bad bug fixed.

Merge pull request !58 from 王帅/main
This commit is contained in:
Michael Yang 2023-06-09 22:51:00 +00:00 committed by Gitee
commit 84c06feaca
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9 changed files with 261 additions and 162 deletions

View File

@ -652,75 +652,26 @@ public interface BaseMapper<T> {
default <R> Page<R> paginateAs(Page<R> page, QueryWrapper queryWrapper, Class<R> asType, Consumer<FieldQueryBuilder<R>>... consumers) {
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
List<Join> joins = CPI.getJoins(queryWrapper);
boolean removedJoins = true;
// 只有 totalRow 小于 0 的时候才会去查询总量
// 这样方便用户做总数缓存而非每次都要去查询总量
// 一般的分页场景中只有第一页的时候有必要去查询总量第二页以后是不需要的
if (page.getTotalRow() < 0) {
//移除 select
CPI.setSelectColumns(queryWrapper, Collections.singletonList(count().as("total")));
//移除 OrderBy
if (CollectionUtil.isNotEmpty(orderBys)) {
CPI.setOrderBys(queryWrapper, null);
}
//移除 left join
if (joins != null && !joins.isEmpty()) {
for (Join join : joins) {
if (!Join.TYPE_LEFT.equals(CPI.getJoinType(join))) {
removedJoins = false;
break;
}
}
} else {
removedJoins = false;
}
if (removedJoins) {
List<String> joinTables = new ArrayList<>();
joins.forEach(join -> {
QueryTable joinQueryTable = CPI.getJoinQueryTable(join);
if (joinQueryTable != null && StringUtil.isNotBlank(joinQueryTable.getName())) {
joinTables.add(joinQueryTable.getName());
}
});
QueryCondition where = CPI.getWhereQueryCondition(queryWrapper);
if (CPI.containsTable(where, CollectionUtil.toArrayString(joinTables))) {
removedJoins = false;
}
}
if (removedJoins) {
CPI.setJoins(queryWrapper, null);
}
queryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper);
long count = selectCountByQuery(queryWrapper);
page.setTotalRow(count);
}
if (page.getTotalRow() == 0 || page.getPageNumber() > page.getTotalPage()) {
if (page.isEmpty()) {
return page;
}
//重置 selectColumns
CPI.setSelectColumns(queryWrapper, selectColumns);
//重置 orderBys
if (CollectionUtil.isNotEmpty(orderBys)) {
CPI.setOrderBys(queryWrapper, orderBys);
}
CPI.setOrderBys(queryWrapper, orderBys);
//重置 join
if (removedJoins) {
CPI.setJoins(queryWrapper, joins);
}
CPI.setJoins(queryWrapper, joins);
int offset = page.getPageSize() * (page.getPageNumber() - 1);
queryWrapper.limit(offset, page.getPageSize());
@ -734,6 +685,11 @@ public interface BaseMapper<T> {
MapperUtil.queryFields(this, records, consumers);
page.setRecords(records);
}
// 将之前设置的 limit 清除掉
CPI.setLimitRows(queryWrapper, null);
CPI.setLimitOffset(queryWrapper, null);
return page;
}

View File

@ -1,17 +1,17 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;
@ -128,6 +128,14 @@ public class Page<T> implements Serializable {
}
}
public boolean isEmpty() {
return getTotalRow() == 0 || getPageNumber() > getTotalPage();
}
public boolean hasNext() {
return getTotalPage() != 0 && getPageNumber() < getTotalPage();
}
public <R> Page<R> map(Function<? super T, ? extends R> mapper) {
Page<R> newPage = new Page<>();

View File

@ -1,17 +1,17 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.query;
@ -33,6 +33,11 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
}
public QueryWrapper select() {
return this;
}
public QueryWrapper select(String... columns) {
for (String column : columns) {
addSelectColumn(new StringQueryColumn(column));

View File

@ -1,17 +1,17 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.query;
@ -51,7 +51,7 @@ public class SelectQueryTable extends QueryTable {
if (StringUtil.isNotBlank(alias)) {
return "(" + sql + ") AS " + dialect.wrap(alias);
} else {
return sql;
return "(" + sql + ")";
}
}
}

View File

@ -1,17 +1,17 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.row;
@ -21,11 +21,14 @@ import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.provider.RowSqlProvider;
import com.mybatisflex.core.query.*;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.MapperUtil;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.exceptions.TooManyResultsException;
import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.mybatisflex.core.query.QueryMethods.count;
@ -435,78 +438,37 @@ public interface RowMapper {
List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
List<Join> joins = CPI.getJoins(queryWrapper);
boolean removedJoins = true;
// 只有 totalRow 小于 0 的时候才会去查询总量
// 这样方便用户做总数缓存而非每次都要去查询总量
// 一般的分页场景中只有第一页的时候有必要去查询总量第二页以后是不需要的
if (page.getTotalRow() < 0) {
//移除 seelct
CPI.setSelectColumns(queryWrapper, Collections.singletonList(count().as("total")));
//移除 OrderBy
if (CollectionUtil.isNotEmpty(orderBys)) {
CPI.setOrderBys(queryWrapper, null);
}
//移除 left join
if (joins != null && !joins.isEmpty()) {
for (Join join : joins) {
if (!Join.TYPE_LEFT.equals(CPI.getJoinType(join))) {
removedJoins = false;
break;
}
}
} else {
removedJoins = false;
}
if (removedJoins) {
List<String> joinTables = new ArrayList<>();
joins.forEach(join -> {
QueryTable joinQueryTable = CPI.getJoinQueryTable(join);
if (joinQueryTable != null && StringUtil.isNotBlank(joinQueryTable.getName())) {
joinTables.add(joinQueryTable.getName());
}
});
QueryCondition where = CPI.getWhereQueryCondition(queryWrapper);
if (CPI.containsTable(where, CollectionUtil.toArrayString(joinTables))) {
removedJoins = false;
}
}
if (removedJoins) {
CPI.setJoins(queryWrapper, null);
}
queryWrapper = MapperUtil.optimizeCountQueryWrapper(queryWrapper);
long count = selectCountByQuery(schema,tableName, queryWrapper);
page.setTotalRow(count);
}
if (page.getTotalRow() == 0 || page.getPageNumber() > page.getTotalPage()) {
if (page.isEmpty()) {
return page;
}
//重置 selectColumns
CPI.setSelectColumns(queryWrapper, selectColumns);
//重置 orderBys
if (CollectionUtil.isNotEmpty(orderBys)) {
CPI.setOrderBys(queryWrapper, orderBys);
}
CPI.setOrderBys(queryWrapper, orderBys);
//重置 join
if (removedJoins) {
CPI.setJoins(queryWrapper, joins);
}
CPI.setJoins(queryWrapper, joins);
int offset = page.getPageSize() * (page.getPageNumber() - 1);
queryWrapper.limit(offset, page.getPageSize());
List<Row> records = selectListByQuery(schema,tableName, queryWrapper);
page.setRecords(records);
// 将之前设置的 limit 清除掉
CPI.setLimitRows(queryWrapper, null);
CPI.setLimitOffset(queryWrapper, null);
return page;
}

View File

@ -105,6 +105,10 @@ public class ClassUtil {
|| clazz == double[].class;
}
public static boolean canInstance(int mod) {
return !Modifier.isAbstract(mod) || !Modifier.isInterface(mod);
}
public static <T> T newInstance(Class<T> clazz) {
try {

View File

@ -18,19 +18,112 @@ package com.mybatisflex.core.util;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.field.FieldQuery;
import com.mybatisflex.core.field.FieldQueryBuilder;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.query.*;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import java.util.*;
import java.util.function.Consumer;
import static com.mybatisflex.core.query.QueryMethods.count;
public class MapperUtil {
private MapperUtil() {
}
/**
* <p>原生的未经过优化的 COUNT 查询抛开效率问题不谈只关注结果的准确性
* 这个 COUNT 查询查出来的分页总数据是 100% 正确的不接受任何反驳
*
* <p>为什么这么说因为是用子查询实现的生成的 SQL 如下
*
* <p><pre>
* {@code
* SELECT COUNT(*) AS `total` FROM ( ...用户构建的 SQL 语句... );
* }
* </pre>
*
* <p>不进行 SQL 优化的时候返回的就是这样的 COUNT 查询语句
*/
public static QueryWrapper rawCountQueryWrapper(QueryWrapper queryWrapper) {
return QueryWrapper.create()
.select(count().as("total"))
.from(queryWrapper);
}
/**
* 优化 COUNT 查询语句
*/
public static QueryWrapper optimizeCountQueryWrapper(QueryWrapper queryWrapper) {
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
List<QueryColumn> groupByColumns = CPI.getGroupByColumns(queryWrapper);
// 如果有 distinct 语句或者 group by 语句则不优化
// 这种一旦优化了就会造成 count 语句查询出来的值不对
if (hasDistinct(selectColumns) || hasGroupBy(groupByColumns)) {
return rawCountQueryWrapper(queryWrapper);
}
// 判断能不能清除 join 语句
if (canClearJoins(queryWrapper)) {
CPI.setJoins(queryWrapper, null);
}
// 最后将最后面的 order by 移除掉
// select 里面的列换成 COUNT(*) AS `total` 就好了
CPI.setOrderBys(queryWrapper, null);
CPI.setSelectColumns(queryWrapper, Collections.singletonList(count().as("total")));
return queryWrapper;
}
private static boolean hasDistinct(List<QueryColumn> selectColumns) {
if (CollectionUtil.isEmpty(selectColumns)) {
return false;
}
for (QueryColumn selectColumn : selectColumns) {
if (selectColumn instanceof DistinctQueryColumn) {
return true;
}
}
return false;
}
private static boolean hasGroupBy(List<QueryColumn> groupByColumns) {
return CollectionUtil.isNotEmpty(groupByColumns);
}
private static boolean canClearJoins(QueryWrapper queryWrapper) {
List<Join> joins = CPI.getJoins(queryWrapper);
if (CollectionUtil.isEmpty(joins)) {
return false;
}
// 只有全是 left join 语句才会清除 join
// 因为如果是 inner join right join 往往都会放大记录数
for (Join join : joins) {
if (!Join.TYPE_LEFT.equals(CPI.getJoinType(join))) {
return false;
}
}
// 获取 join 语句中使用到的表名
List<String> joinTables = new ArrayList<>();
// Map<String, String> joinTables = new HashMap<>();
joins.forEach(join -> {
QueryTable joinQueryTable = CPI.getJoinQueryTable(join);
if (joinQueryTable != null && StringUtil.isNotBlank(joinQueryTable.getName())) {
joinTables.add(joinQueryTable.getName());
}
});
// 获取 where 语句中的条件
QueryCondition where = CPI.getWhereQueryCondition(queryWrapper);
// 最后判断一下 where 中是否用到了 join 的表
return !CPI.containsTable(where, CollectionUtil.toArrayString(joinTables));
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static <R> void queryFields(BaseMapper<?> mapper, List<R> list, Consumer<FieldQueryBuilder<R>>[] consumers) {
if (CollectionUtil.isEmpty(list) || ArrayUtil.isEmpty(consumers) || consumers[0] == null) {
return;
@ -69,6 +162,10 @@ public class MapperUtil {
private static Class<?> getWrapType(Class<?> type) {
if (ClassUtil.canInstance(type.getModifiers())) {
return type;
}
if (List.class.isAssignableFrom(type)) {
return ArrayList.class;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.test.common;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.MapperUtil;
import org.junit.jupiter.api.Test;
import static com.mybatisflex.test.model.table.RoleTableDef.ROLE;
import static com.mybatisflex.test.model.table.UserRoleTableDef.USER_ROLE;
import static com.mybatisflex.test.model.table.UserTableDef.USER;
/**
* @author 王帅
* @since 2023-06-09
*/
class MapperUtilTest {
@Test
void testOptimizeCountQueryWrapper() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
.from(USER.as("u"))
.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))
.where(USER.USER_ID.eq(3))
.and(USER_ROLE.ROLE_ID.eq(6))
.groupBy(ROLE.ROLE_ID);
System.out.println(queryWrapper.toSQL());
System.out.println(MapperUtil.rawCountQueryWrapper(queryWrapper).toSQL());
System.out.println(MapperUtil.optimizeCountQueryWrapper(queryWrapper).toSQL());
}
}

View File

@ -16,6 +16,7 @@
package com.mybatisflex.test.mapper;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.test.model.UserInfo;
import com.mybatisflex.test.model.UserVO;
@ -39,6 +40,7 @@ import static com.mybatisflex.test.model.table.UserTableDef.USER;
* @since 2023-06-07
*/
@SpringBootTest
@SuppressWarnings("all")
class UserMapperTest {
@Autowired
@ -114,4 +116,21 @@ class UserMapperTest {
userInfos.forEach(System.err::println);
}
@Test
void testPage() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
.from(USER.as("u"))
.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<UserVO> page;
int pageNumber = 0;
do {
page = Page.of(++pageNumber, 1);
page = userMapper.paginateAs(page, queryWrapper, UserVO.class);
System.err.println(page);
} while (page.hasNext());
}
}