diff --git a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-base/src/main/java/com/github/yulichang/adapter/base/IAdapter.java b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-base/src/main/java/com/github/yulichang/adapter/base/IAdapter.java index aa1ab1e..770dfe7 100644 --- a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-base/src/main/java/com/github/yulichang/adapter/base/IAdapter.java +++ b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-base/src/main/java/com/github/yulichang/adapter/base/IAdapter.java @@ -75,4 +75,7 @@ public interface IAdapter { } return typeHandler; } + + default void checkCollectionPage() { + } } diff --git a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v33x/src/main/java/com/github/yulichang/adapter/v33x/Adapter33x.java b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v33x/src/main/java/com/github/yulichang/adapter/v33x/Adapter33x.java index 94023e3..80a34b0 100644 --- a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v33x/src/main/java/com/github/yulichang/adapter/v33x/Adapter33x.java +++ b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v33x/src/main/java/com/github/yulichang/adapter/v33x/Adapter33x.java @@ -3,6 +3,7 @@ package com.github.yulichang.adapter.v33x; import com.baomidou.mybatisplus.core.MybatisPlusVersion; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.github.yulichang.adapter.base.IAdapter; @@ -87,4 +88,9 @@ public class Adapter33x implements IAdapter { } return typeHandler; } + + @Override + public void checkCollectionPage() { + throw ExceptionUtils.mpe("page by main need MP version 3.5.6+, current version: " + MybatisPlusVersion.getVersion()); + } } diff --git a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v3431/src/main/java/com/github/yulichang/adapter/v3431/Adapter3431.java b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v3431/src/main/java/com/github/yulichang/adapter/v3431/Adapter3431.java index 19edb2e..b1b1820 100644 --- a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v3431/src/main/java/com/github/yulichang/adapter/v3431/Adapter3431.java +++ b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v3431/src/main/java/com/github/yulichang/adapter/v3431/Adapter3431.java @@ -3,6 +3,7 @@ package com.github.yulichang.adapter.v3431; import com.baomidou.mybatisplus.core.MybatisPlusVersion; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.github.yulichang.adapter.base.IAdapter; @@ -60,4 +61,9 @@ public class Adapter3431 implements IAdapter { } return typeHandler; } + + @Override + public void checkCollectionPage() { + throw ExceptionUtils.mpe("page by main need MP version 3.5.6+, current version: " + MybatisPlusVersion.getVersion()); + } } diff --git a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v355/src/main/java/com/github/yulichang/adapter/v355/Adapter355.java b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v355/src/main/java/com/github/yulichang/adapter/v355/Adapter355.java index ed3d412..9e1bbbe 100644 --- a/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v355/src/main/java/com/github/yulichang/adapter/v355/Adapter355.java +++ b/mybatis-plus-join-adapter/mybatis-plus-join-adapter-v355/src/main/java/com/github/yulichang/adapter/v355/Adapter355.java @@ -1,5 +1,7 @@ package com.github.yulichang.adapter.v355; +import com.baomidou.mybatisplus.core.MybatisPlusVersion; +import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; import com.github.yulichang.adapter.base.IAdapter; import com.github.yulichang.adapter.jsqlparser.v46.JSqlParserHelperV46; import org.apache.ibatis.session.Configuration; @@ -29,4 +31,9 @@ public class Adapter355 implements IAdapter { } return typeHandler; } + + @Override + public void checkCollectionPage() { + throw ExceptionUtils.mpe("page by main need MP version 3.5.6+, current version: " + MybatisPlusVersion.getVersion()); + } } \ No newline at end of file diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/config/MPJInterceptorConfig.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/config/MPJInterceptorConfig.java index 4d357d3..d8bc4db 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/config/MPJInterceptorConfig.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/config/MPJInterceptorConfig.java @@ -34,7 +34,6 @@ public class MPJInterceptorConfig { } } - @SuppressWarnings("unchecked") private void replaceInterceptorChain(List sqlSessionFactoryList) { if (CollectionUtils.isEmpty(sqlSessionFactoryList)) { return; @@ -46,6 +45,7 @@ public class MPJInterceptorConfig { InterceptorChain chain = (InterceptorChain) interceptorChain.get(factory.getConfiguration()); Field interceptors = InterceptorChain.class.getDeclaredField("interceptors"); interceptors.setAccessible(true); + @SuppressWarnings("unchecked") List list = (List) interceptors.get(chain); if (CollectionUtils.isEmpty(list)) { interceptors.set(chain, new InterceptorList<>()); diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/extension/apt/AptAbstractWrapper.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/extension/apt/AptAbstractWrapper.java index 9f726c4..5c1efef 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/extension/apt/AptAbstractWrapper.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/extension/apt/AptAbstractWrapper.java @@ -20,8 +20,10 @@ import com.github.yulichang.toolkit.TableHelper; import com.github.yulichang.toolkit.TableMap; import com.github.yulichang.toolkit.support.ColumnCache; import com.github.yulichang.wrapper.interfaces.MFunction; +import com.github.yulichang.wrapper.segments.PageInfo; import com.github.yulichang.wrapper.segments.SelectCache; import lombok.Getter; +import lombok.Setter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -58,6 +60,12 @@ public abstract class AptAbstractWrapper function) { + this.pageByMain = true; + PageInfo.PageInfoBuilder apply = function.apply(PageInfo.builder()); + this.pageInfo = apply.build(); + return typedThis; + } + + public PageInfo getPageInfo() { + return pageInfo == null ? new PageInfo() : pageInfo; + } + /** * 设置表别名 * 设置表别名注意sql注入问题 @@ -405,6 +436,8 @@ public abstract class AptAbstractWrapper function) { + this.pageByMain = true; + PageInfo.PageInfoBuilder apply = function.apply(PageInfo.builder()); + this.pageInfo = apply.build(); + return typedThis; + } + + public PageInfo getPageInfo() { + return pageInfo == null ? new PageInfo() : pageInfo; + } + /** * 设置表别名 * 设置表别名注意sql注入问题 @@ -448,6 +480,8 @@ public abstract class KtAbstractLambdaWrapper MS_MAPPER_CACHE = new ConcurrentHashMap<>(); private static final Map RES_MAPPER_CACHE = new ConcurrentHashMap<>(); + private static final Log log = LogFactory.getLog(MPJInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { @@ -75,7 +77,7 @@ public class MPJInterceptor implements Interceptor { } } if (Objects.nonNull(rt)) { - args[0] = getMappedStatement(ms, rt, ew); + args[0] = getMappedStatement(ms, rt, ew, map); } } } @@ -88,7 +90,7 @@ public class MPJInterceptor implements Interceptor { /** * 获取MappedStatement */ - public MappedStatement getMappedStatement(MappedStatement ms, Class resultType, Object ew) { + public MappedStatement getMappedStatement(MappedStatement ms, Class resultType, Object ew, Map map) { if (ew instanceof SelectWrapper) { SelectWrapper wrapper = (SelectWrapper) ew; if (wrapper.getEntityClass() == null) { @@ -97,6 +99,19 @@ public class MPJInterceptor implements Interceptor { if (wrapper.getSelectColumns().isEmpty() && wrapper.getEntityClass() != null) { wrapper.selectAll(); } + if (wrapper.isResultMapCollection()) { + if (map.values().stream().anyMatch(a -> a instanceof IPage)) { + if (wrapper.isPageByMain()) { + AdapterHelper.getAdapter().checkCollectionPage(); + IPage page = ParameterUtils.findPage(map).orElse(null); + wrapper.getPageInfo().setInnerPage(page); + map.entrySet().removeIf(entry -> entry.getValue() instanceof IPage); + } else { + // 一对多分页问题警告 + log.warn("select one to many and page query will result in errors in the total count statistics, please use xml."); + } + } + } } return buildMappedStatement(ms, resultType, ew); } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/DialectWrapper.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/DialectWrapper.java new file mode 100644 index 0000000..ddf9bcd --- /dev/null +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/DialectWrapper.java @@ -0,0 +1,119 @@ +package com.github.yulichang.interceptor.pagination; + +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; +import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; +import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel; +import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect; +import lombok.Getter; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.*; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.xmltags.DynamicSqlSource; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * @author yulichang + * @since 1.5.0 + */ +public class DialectWrapper { + + private final IDialect dialect; + + @Getter + private final List fullMappings = new ArrayList<>(); + @Getter + private final List pageMappings = new ArrayList<>(); + + @Getter + private String finallySql; + + public DialectWrapper(IDialect dialect) { + this.dialect = dialect; + } + + public void buildPaginationSql(String originalSql, List mappings, long offset, long limit, + Consumer consumers, MappedStatement ms, Object parameter) { + try { + // 解析sql + Select select = (Select) JsqlParserGlobal.parse(originalSql); + if (select instanceof SetOperationList) { + throw ExceptionUtils.mpe("不支持 union 对多分页"); + } + //获取 ? 出现的次数 + String sourceSql = select.toString(); + int count = ParseHelper.countChar(sourceSql); + String formatSql; + if (count == mappings.size()) { + formatSql = ParseHelper.decode(sourceSql); + } else { + SqlSource sqlSource = ms.getSqlSource(); + if (sqlSource instanceof DynamicSqlSource) { + formatSql = ParseHelper.getOriginalSql(parameter, (DynamicSqlSource) sqlSource); + } else { + throw ExceptionUtils.mpe("unknown type: " + sqlSource.getClass().getName()); + } + } + //分页的sql + PlainSelect pageSql = (PlainSelect) JsqlParserGlobal.parse(formatSql); + + List joins = pageSql.getJoins(); + if (CollectionUtils.isNotEmpty(joins)) { + pageSql.setJoins(null); + } + + pageSql.setSelectItems(Collections.singletonList(new SelectItem<>(new Column().withColumnName("*")))); + + // 替换回 ? 同步mappings顺序 + String repSql = pageSql.toString(); + Map sortMap = new HashMap<>(); + repSql = ParseHelper.encode(mappings, count, repSql, sortMap); + this.pageMappings.addAll(sortMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).collect(Collectors.toList())); + + DialectModel dialectModel = dialect.buildPaginationSql(repSql, offset, limit); + consumers.accept(dialectModel); + + // 带分页方言的sql + String formatDialectSql = ParseHelper.decode(dialectModel.getDialectSql(), i -> "s" + i); + + //完整的sql + PlainSelect fullSql = (PlainSelect) JsqlParserGlobal.parse(formatSql); + + PlainSelect finalPlainSelect = new PlainSelect(); + finalPlainSelect.setSelectItems(fullSql.getSelectItems()); + finalPlainSelect.setDistinct(fullSql.getDistinct()); + finalPlainSelect.setFromItem( + new Table() + .withName("(" + formatDialectSql + ")") + .withAlias(new Alias(fullSql.getFromItem().getAlias().getName(), false))); + finalPlainSelect.setJoins(fullSql.getJoins()); + + String finalSql = finalPlainSelect.toString(); + Map finalSortMap = new HashMap<>(); + //page部分 + for (int i = 0; i < count; i++) { + String repStr = ParseHelper.format.apply("s" + i); + int i1 = finalSql.indexOf(repStr); + if (i1 != -1) { + finalSql = finalSql.replace(repStr, "?"); + finalSortMap.put(i1, this.pageMappings.get(i)); + } + } + //其他部分 + finalSql = ParseHelper.encode(mappings, count, finalSql, finalSortMap); + this.fullMappings.addAll(finalSortMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).collect(Collectors.toList())); + this.finallySql = finalSql; + } catch (Exception e) { + throw ExceptionUtils.mpe("not support this sql, please use xml. error sql: " + originalSql); + } + } + + +} diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/PageInnerInterceptor.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/PageInnerInterceptor.java new file mode 100644 index 0000000..ea59792 --- /dev/null +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/PageInnerInterceptor.java @@ -0,0 +1,252 @@ +package com.github.yulichang.interceptor.pagination; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect; +import com.github.yulichang.wrapper.interfaces.SelectWrapper; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.*; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.xmltags.DynamicSqlSource; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author yulichang + * @since 1.5.0 + */ +public class PageInnerInterceptor extends PaginationInnerInterceptor { + + + private static final Log log = LogFactory.getLog(PageInnerInterceptor.class); + + public PageInnerInterceptor(PaginationInnerInterceptor pagination) { + super.setOptimizeJoin(true); + super.setDbType(pagination.getDbType()); + super.setDialect(pagination.getDialect()); + super.setOverflow(pagination.isOverflow()); + super.setMaxLimit(pagination.getMaxLimit()); + } + + /** + * 执行count + */ + @Override + public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { + //没有wrapper 或者 不是对多查询 不做处理 + SelectWrapper wrapper = findMPJWrapper(parameter).orElse(null); + if (wrapper == null || !wrapper.isPageByMain()) { + return true; + } + // copy super + IPage page = wrapper.getPageInfo().getInnerPage(); + if (page == null || page.getSize() < 0 || !page.searchCount() || resultHandler != Executor.NO_RESULT_HANDLER) { + return true; + } + + BoundSql countSql; + MappedStatement countMs = buildCountMappedStatement(ms, page.countId()); + if (countMs != null) { + countSql = countMs.getBoundSql(parameter); + } else { + countMs = buildAutoCountMappedStatement(ms); + ArrayList mappingArrayList = new ArrayList<>(boundSql.getParameterMappings()); + String countSqlStr = autoCountSql(boundSql.getSql(), mappingArrayList, ms, parameter, wrapper); + PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql); + countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mappingArrayList, parameter); + PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters()); + } + + CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql); + List result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql); + long total = 0; + if (CollectionUtils.isNotEmpty(result)) { + // 个别数据库 count 没数据不会返回 0 + Object o = result.get(0); + if (o != null) { + total = Long.parseLong(o.toString()); + } + } + page.setTotal(total); + return continuePage(page); + } + + /** + * 添加分页方言 + */ + @Override + public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { + SelectWrapper wrapper = findMPJWrapper(parameter).orElse(null); + if (wrapper == null || !wrapper.isPageByMain()) { + return; + } + // copy super + IPage page = wrapper.getPageInfo().getInnerPage(); + if (null == page) { + return; + } + + // 处理 orderBy 拼接 + boolean addOrdered = false; + String buildSql = boundSql.getSql(); + List orders = page.orders(); + if (CollectionUtils.isNotEmpty(orders)) { + addOrdered = true; + buildSql = this.concatOrderBy(buildSql, orders); + } + + // size 小于 0 且不限制返回值则不构造分页sql + Long _limit = page.maxLimit() != null ? page.maxLimit() : maxLimit; + if (page.getSize() < 0 && null == _limit) { + if (addOrdered) { + PluginUtils.mpBoundSql(boundSql).sql(buildSql); + } + return; + } + + handlerLimit(page, _limit); + DialectWrapper dialect = findMPJDialect(executor); + + final Configuration configuration = ms.getConfiguration(); + PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql); + Map additionalParameter = mpBoundSql.additionalParameters(); + dialect.buildPaginationSql(buildSql, boundSql.getParameterMappings(), page.offset(), page.getSize(), + c -> c.consumers(dialect.getPageMappings(), configuration, additionalParameter), + ms, parameter); + mpBoundSql.sql(dialect.getFinallySql()); + mpBoundSql.parameterMappings(dialect.getFullMappings()); + } + + @Override + public void setProperties(Properties properties) { + super.setProperties(properties); + } + + public String autoCountSql(String sql, List mappings, MappedStatement ms, Object parameter, SelectWrapper wrapper) { + try { + Select select = (Select) JsqlParserGlobal.parse(sql); + if (select instanceof SetOperationList) { + throw ExceptionUtils.mpe("不支持 union 对多分页"); + } + + //获取 ? 出现的次数 + String sourceSql = select.toString(); + int count = ParseHelper.countChar(sourceSql); + + String formatSql; + if (count == mappings.size()) { + formatSql = ParseHelper.decode(sourceSql); + } else { + SqlSource sqlSource = ms.getSqlSource(); + if (sqlSource instanceof DynamicSqlSource) { + formatSql = ParseHelper.getOriginalSql(parameter, (DynamicSqlSource) sqlSource); + } else { + log.error("unknown type: " + sqlSource.getClass().getName()); + throw ExceptionUtils.mpe("not support this sql, please use xml. error sql: " + sql); + } + } + + PlainSelect ps = (PlainSelect) JsqlParserGlobal.parse(formatSql); + + // 优化 order by 在非分组情况下 + List orderBy = ps.getOrderByElements(); + if (CollectionUtils.isNotEmpty(orderBy)) { + boolean canClean = true; + for (OrderByElement order : orderBy) { + // order by 里带参数,不去除order by + Expression expression = order.getExpression(); + if (!(expression instanceof Column) && expression.toString().contains(StringPool.QUESTION_MARK)) { + canClean = false; + break; + } + } + if (canClean) { + ps.setOrderByElements(null); + } + } + + List joins = ps.getJoins(); + if (CollectionUtils.isNotEmpty(joins)) { + ps.setJoins(null); + } + + String repSql; + Distinct distinct = ps.getDistinct(); + GroupByElement groupBy = ps.getGroupBy(); + String mainAlias = Optional.of(ps.getFromItem().getAlias().getName()).orElse(""); + // 包含 distinct、groupBy 不优化 + if (null != distinct || null != groupBy) { + if (wrapper.getPageInfo().getCountSelectSql() != null) { + ps.setSelectItems(Collections.singletonList(new SelectItem<>(new Column().withColumnName(wrapper.getPageInfo().getCountSelectSql())))); + } else { + ps.getSelectItems().removeIf(s -> { + if (s.getExpression() instanceof Column) { + Column column = (Column) s.getExpression(); + return !mainAlias.equals(column.getTable().toString()); + } + return false; + }); + } + repSql = lowLevelCountSql(ps.toString()); + } else { + // 优化 SQL + ps.setSelectItems(COUNT_SELECT_ITEM); + repSql = ps.toString(); + } + + // 替换回 ? 同步mappings顺序 + Map sortMap = new HashMap<>(); + repSql = ParseHelper.encode(mappings, count, repSql, sortMap); + mappings.clear(); + mappings.addAll(sortMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).collect(Collectors.toList())); + return repSql; + } catch (JSQLParserException e) { + logger.error("optimize this sql to a count sql has exception, sql:\"" + sql + "\", exception:\n" + e.getCause()); + } catch (Exception e) { + logger.error("optimize this sql to a count sql has error, sql:\"" + sql + "\", exception:\n" + e); + } + throw ExceptionUtils.mpe("not support this sql, please use xml. error sql: " + sql); + } + + protected DialectWrapper findMPJDialect(Executor executor) { + IDialect dialect = super.findIDialect(executor); + return new DialectWrapper(dialect); + } + + public static Optional> findMPJWrapper(Object parameterObject) { + if (parameterObject != null) { + if (parameterObject instanceof Map) { + Map parameterMap = (Map) parameterObject; + for (Map.Entry entry : parameterMap.entrySet()) { + if (entry.getValue() != null && entry.getValue() instanceof SelectWrapper) { + return Optional.of((SelectWrapper) entry.getValue()); + } + } + } else if (parameterObject instanceof SelectWrapper) { + return Optional.of((SelectWrapper) parameterObject); + } + } + return Optional.empty(); + } + +} diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java new file mode 100644 index 0000000..f996c71 --- /dev/null +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/ParseHelper.java @@ -0,0 +1,111 @@ +package com.github.yulichang.interceptor.pagination; + +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.github.yulichang.toolkit.ReflectionKit; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.xmltags.DynamicSqlSource; +import org.apache.ibatis.scripting.xmltags.SqlNode; +import org.apache.ibatis.session.Configuration; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * sql 分页解析工具类 + * + * @author yulichang + * @since 1.5.0 + */ +@SuppressWarnings("unused") +public final class ParseHelper { + + private static final Map SQL_SOURCE_CACHE = new ConcurrentHashMap<>(); + + private static final String prefix = "MPJ_Param_i_"; + private static final String suffix = "_MPJ_Param_i"; + private static final char placeholder_char = '?'; + private static final String placeholder_str = String.valueOf(placeholder_char); + + public static final Function format = index -> prefix + index + suffix; + + public static int countChar(String str) { + return countChar(str, placeholder_char); + } + + public static int countChar(String str, String tag) { + return countChar(str, tag.charAt(0)); + } + + public static int countChar(String str, char tag) { + int count = 0; + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == tag) { + count++; + } + } + return count; + } + + public static String decode(String str) { + return decode(str, null); + } + + public static String decode(String str, Function formatter) { + return decode(str, formatter, placeholder_char); + } + + public static String decode(String str, Function formatter, char placeholder) { + StringBuilder sb = new StringBuilder(); + int count = 0; + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == placeholder) { + sb.append(format.apply(formatter == null ? count : formatter.apply(count))); + count++; + } else { + sb.append(str.charAt(i)); + } + } + return sb.toString(); + } + + public static String decode(String str, Function formatter, String placeholder) { + StringBuilder sb = new StringBuilder(); + String[] split = str.split(placeholder); + for (int i = 0; i < split.length; i++) { + sb.append(split[i]); + if (i < split.length - 1) { + sb.append(format.apply(formatter == null ? i : formatter.apply(i))); + } + } + return sb.toString(); + } + + public static String encode(List mappings, int count, String repSql, Map sortMap) { + return encode(mappings, count, repSql, sortMap, placeholder_str); + } + + public static String encode(List mappings, int count, String repSql, Map sortMap, String placeholder) { + for (int i = 0; i < count; i++) { + String repStr = ParseHelper.format.apply(i); + int i1 = repSql.indexOf(repStr); + if (i1 != -1) { + repSql = repSql.replace(repStr, placeholder); + sortMap.put(i1, mappings.get(i)); + } + } + return repSql; + } + + public static String getOriginalSql(Object parameter, DynamicSqlSource sqlSource) { + Assert.notNull(sqlSource, "sqlSource must not be null"); + SqlSourceWrapper sqlSourceWrapper = SQL_SOURCE_CACHE.computeIfAbsent(sqlSource, key -> { + Configuration configuration = ReflectionKit.getFieldValue(sqlSource, "configuration"); + SqlNode sqlNode = ReflectionKit.getFieldValue(sqlSource, "rootSqlNode"); + return new SqlSourceWrapper(configuration, sqlNode); + }); + return ParseHelper.decode(sqlSourceWrapper.getSql(parameter), null, ParseHelper.format.apply("")); + } +} diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/SqlSourceWrapper.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/SqlSourceWrapper.java new file mode 100644 index 0000000..f26de65 --- /dev/null +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/interceptor/pagination/SqlSourceWrapper.java @@ -0,0 +1,159 @@ +package com.github.yulichang.interceptor.pagination; + +import org.apache.ibatis.builder.BaseBuilder; +import org.apache.ibatis.builder.BuilderException; +import org.apache.ibatis.builder.ParameterExpression; +import org.apache.ibatis.builder.SqlSourceBuilder; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.parsing.GenericTokenParser; +import org.apache.ibatis.parsing.TokenHandler; +import org.apache.ibatis.reflection.MetaClass; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.scripting.xmltags.DynamicContext; +import org.apache.ibatis.scripting.xmltags.SqlNode; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.JdbcType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author yulichang + * @since 1.5.0 + */ +public class SqlSourceWrapper { + + private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName"; + + private final Configuration configuration; + private final SqlNode rootSqlNode; + + public SqlSourceWrapper(Configuration configuration, SqlNode rootSqlNode) { + this.configuration = configuration; + this.rootSqlNode = rootSqlNode; + } + + + public String getSql(Object parameterObject) { + DynamicContext context = new DynamicContext(configuration, parameterObject); + rootSqlNode.apply(context); + Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); + return parse(context.getSql(), parameterType, context.getBindings()); + } + + public String parse(String originalSql, Class parameterType, Map additionalParameters) { + ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, + additionalParameters) { + @Override + public String handleToken(String content) { + super.handleToken(content); + return ParseHelper.format.apply(""); + } + }; + GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); + String sql; + if (configuration.isShrinkWhitespacesInSql()) { + sql = parser.parse(SqlSourceBuilder.removeExtraWhitespaces(originalSql)); + } else { + sql = parser.parse(originalSql); + } + return sql; + } + + /** + * copy {@link org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler} + */ + @SuppressWarnings("all") + private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler { + + private final List parameterMappings = new ArrayList<>(); + private final Class parameterType; + private final MetaObject metaParameters; + + public ParameterMappingTokenHandler(Configuration configuration, Class parameterType, + Map additionalParameters) { + super(configuration); + this.parameterType = parameterType; + this.metaParameters = configuration.newMetaObject(additionalParameters); + } + + public List getParameterMappings() { + return parameterMappings; + } + + @Override + public String handleToken(String content) { + parameterMappings.add(buildParameterMapping(content)); + return "?"; + } + + private ParameterMapping buildParameterMapping(String content) { + Map propertiesMap = parseParameterMapping(content); + String property = propertiesMap.get("property"); + Class propertyType; + if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params + propertyType = metaParameters.getGetterType(property); + } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) { + propertyType = parameterType; + } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { + propertyType = java.sql.ResultSet.class; + } else if (property == null || Map.class.isAssignableFrom(parameterType)) { + propertyType = Object.class; + } else { + MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory()); + if (metaClass.hasGetter(property)) { + propertyType = metaClass.getGetterType(property); + } else { + propertyType = Object.class; + } + } + ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType); + Class javaType = propertyType; + String typeHandlerAlias = null; + for (Map.Entry entry : propertiesMap.entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + if ("javaType".equals(name)) { + javaType = resolveClass(value); + builder.javaType(javaType); + } else if ("jdbcType".equals(name)) { + builder.jdbcType(resolveJdbcType(value)); + } else if ("mode".equals(name)) { + builder.mode(resolveParameterMode(value)); + } else if ("numericScale".equals(name)) { + builder.numericScale(Integer.valueOf(value)); + } else if ("resultMap".equals(name)) { + builder.resultMapId(value); + } else if ("typeHandler".equals(name)) { + typeHandlerAlias = value; + } else if ("jdbcTypeName".equals(name)) { + builder.jdbcTypeName(value); + } else if ("property".equals(name)) { + // Do Nothing + } else if ("expression".equals(name)) { + throw new BuilderException("Expression based parameters are not supported yet"); + } else { + throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + + "}. Valid properties are " + PARAMETER_PROPERTIES); + } + } + if (typeHandlerAlias != null) { + builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias)); + } + return builder.build(); + } + + private Map parseParameterMapping(String content) { + try { + return new ParameterExpression(content); + } catch (BuilderException ex) { + throw ex; + } catch (Exception ex) { + throw new BuilderException("Parsing error was found in mapping #{" + content + + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex); + } + } + } + +} diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/InterceptorList.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/InterceptorList.java index c113632..5ba39ab 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/InterceptorList.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/InterceptorList.java @@ -1,10 +1,19 @@ package com.github.yulichang.toolkit; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.github.yulichang.adapter.AdapterHelper; import com.github.yulichang.interceptor.MPJInterceptor; +import com.github.yulichang.interceptor.pagination.PageInnerInterceptor; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.plugin.Interceptor; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.function.Predicate; /** @@ -17,6 +26,8 @@ import java.util.function.Predicate; */ public class InterceptorList extends ArrayList { + private static final Log log = LogFactory.getLog(InterceptorList.class); + public InterceptorList() { super(); } @@ -29,6 +40,41 @@ public class InterceptorList extends ArrayList { super.removeIf(predicate); super.add(mpjInterceptor); } + try { + AdapterHelper.getAdapter().checkCollectionPage(); + } catch (Exception e) { + return; + } + if (this.stream().anyMatch(i -> i instanceof MybatisPlusInterceptor)) { + MybatisPlusInterceptor mybatisPlusInterceptor = super.stream().filter(i -> i instanceof MybatisPlusInterceptor) + .map(i -> (MybatisPlusInterceptor) i).findFirst().orElse(null); + if (mybatisPlusInterceptor != null) { + try { + Field field = MybatisPlusInterceptor.class.getDeclaredField("interceptors"); + field.setAccessible(true); + @SuppressWarnings("unchecked") + List interceptors = (List) field.get(mybatisPlusInterceptor); + + Integer index = null; + PaginationInnerInterceptor paginationInnerInterceptor = null; + if (interceptors.stream().noneMatch(i -> i instanceof PageInnerInterceptor)) { + for (int i = 0; i < interceptors.size(); i++) { + InnerInterceptor innerInterceptor = interceptors.get(i); + if (innerInterceptor instanceof PaginationInnerInterceptor) { + paginationInnerInterceptor = (PaginationInnerInterceptor) innerInterceptor; + index = i; + break; + } + } + } + if (index != null) { + interceptors.add(index + 1, new PageInnerInterceptor(paginationInnerInterceptor)); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + } + } } @Override diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java index 8cbc3ed..5680df5 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/toolkit/ReflectionKit.java @@ -71,16 +71,13 @@ public final class ReflectionKit { * @param fieldName 字段名称 * @return 属性值 */ - public static Object getFieldValue(Object entity, String fieldName) { - Class cls = entity.getClass(); - Map fieldMaps = getFieldMap(cls); + public static T getFieldValue(Object entity, String fieldName) { try { - Field field = fieldMaps.get(fieldName); - Assert.notNull(field, "Error: NoSuchField in %s for %s. Cause:", cls.getSimpleName(), fieldName); + Field field = entity.getClass().getDeclaredField(fieldName); field.setAccessible(true); - return field.get(entity); + return (T) field.get(entity); } catch (ReflectiveOperationException e) { - throw ExceptionUtils.mpe("Error: Cannot read field in %s. Cause:", e, cls.getSimpleName()); + throw ExceptionUtils.mpe("Error: Cannot read field in %s. Cause:", e, entity.getClass().getSimpleName()); } } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/JoinAbstractLambdaWrapper.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/JoinAbstractLambdaWrapper.java index 9485bbb..72ca0e8 100644 --- a/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/JoinAbstractLambdaWrapper.java +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/JoinAbstractLambdaWrapper.java @@ -14,9 +14,12 @@ import com.github.yulichang.config.enums.LogicDelTypeEnum; import com.github.yulichang.toolkit.*; import com.github.yulichang.toolkit.support.ColumnCache; import com.github.yulichang.wrapper.enums.PrefixEnum; +import com.github.yulichang.wrapper.interfaces.MFunction; import com.github.yulichang.wrapper.interfaces.QueryJoin; +import com.github.yulichang.wrapper.segments.PageInfo; import com.github.yulichang.wrapper.segments.SelectCache; import lombok.Getter; +import lombok.Setter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -57,6 +60,12 @@ public abstract class JoinAbstractLambdaWrapper function) { + this.pageByMain = true; + PageInfo.PageInfoBuilder apply = function.apply(PageInfo.builder()); + this.pageInfo = apply.build(); + return typedThis; + } + + public PageInfo getPageInfo() { + return pageInfo == null ? new PageInfo() : pageInfo; + } + /** * 设置表别名 * 设置表别名注意sql注入问题 @@ -475,6 +507,8 @@ public abstract class JoinAbstractLambdaWrapper { String getFrom(); String getAlias(); + + boolean isResultMapCollection(); + + PageInfo getPageInfo(); + + boolean isPageByMain(); } diff --git a/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/segments/PageInfo.java b/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/segments/PageInfo.java new file mode 100644 index 0000000..9004c62 --- /dev/null +++ b/mybatis-plus-join-core/src/main/java/com/github/yulichang/wrapper/segments/PageInfo.java @@ -0,0 +1,50 @@ +package com.github.yulichang.wrapper.segments; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author yulichang + * @since 1.5.0 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageInfo implements Serializable { + + private IPage innerPage; + + private String countSelectSql; + + public static PageInfoBuilder builder() { + return new PageInfoBuilder(); + } + + + @SuppressWarnings("unused") + public static class PageInfoBuilder { + private IPage innerPage; + private String countSelectSql; + + PageInfoBuilder() { + } + + public PageInfoBuilder innerPage(IPage innerPage) { + this.innerPage = innerPage; + return this; + } + + public PageInfoBuilder countSelectSql(String countSelectSql) { + this.countSelectSql = countSelectSql; + return this; + } + + public PageInfo build() { + return new PageInfo(this.innerPage, this.countSelectSql); + } + } +} diff --git a/mybatis-plus-join-test/test-base/src/main/java/com/github/yulichang/test/config/MybatisPlusConfig.java b/mybatis-plus-join-test/test-base/src/main/java/com/github/yulichang/test/config/MybatisPlusConfig.java index b92ed87..241bf85 100644 --- a/mybatis-plus-join-test/test-base/src/main/java/com/github/yulichang/test/config/MybatisPlusConfig.java +++ b/mybatis-plus-join-test/test-base/src/main/java/com/github/yulichang/test/config/MybatisPlusConfig.java @@ -51,8 +51,6 @@ public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); - PaginationInnerInterceptor page = new PaginationInnerInterceptor(); - page.setOptimizeJoin(false); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { @@ -64,7 +62,11 @@ public class MybatisPlusConfig { return !tableName.startsWith("user_tenant"); } })); + + PaginationInnerInterceptor page = new PaginationInnerInterceptor(); + page.setOptimizeJoin(false); interceptor.addInnerInterceptor(page); + interceptor.addInnerInterceptor(new SqlInterceptor()); return interceptor; } diff --git a/mybatis-plus-join-test/test-join/src/main/resources/application.yml b/mybatis-plus-join-test/test-join/src/main/resources/application.yml index d68dc61..a241567 100644 --- a/mybatis-plus-join-test/test-join/src/main/resources/application.yml +++ b/mybatis-plus-join-test/test-join/src/main/resources/application.yml @@ -15,8 +15,8 @@ mybatis-plus: logic-delete-value: true logic-not-delete-value: false banner: true -# configuration: -# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus-join: # 打印 mybatis plus join banner @@ -26,9 +26,9 @@ mybatis-plus-join: logic-del-type: where -logging: - level: - com.github.yulichang: debug +#logging: +# level: +# com.github.yulichang: debug --- spring: diff --git a/mybatis-plus-join-test/test-join/src/test/java/com/github/yulichang/test/join/unit/PageByMainTest.java b/mybatis-plus-join-test/test-join/src/test/java/com/github/yulichang/test/join/unit/PageByMainTest.java new file mode 100644 index 0000000..6084be8 --- /dev/null +++ b/mybatis-plus-join-test/test-join/src/test/java/com/github/yulichang/test/join/unit/PageByMainTest.java @@ -0,0 +1,109 @@ +package com.github.yulichang.test.join.unit; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.test.join.dto.UserDTO; +import com.github.yulichang.test.join.entity.AddressDO; +import com.github.yulichang.test.join.entity.UserDO; +import com.github.yulichang.test.util.ThreadLocalUtils; +import com.github.yulichang.toolkit.JoinWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class PageByMainTest { + + @Test + void pageByMain() { + ThreadLocalUtils.set("SELECT t.id, t.pid, t.`name`, t.`json`, t.sex, t.head_img, t.create_time, t.address_id, " + + "t.address_id2, t.del, t.create_by, t.update_by, t1.id AS joina_id, t1.user_id, t1.area_id, t1.tel, " + + "t1.address, t1.del AS joina_del FROM (SELECT * FROM `user` t WHERE t.del = false AND (t.id <= ?) LIMIT ?) t " + + "LEFT JOIN address t1 ON (t1.user_id = t.id AND t1.user_id >= ? AND t1.del = false)"); + MPJLambdaWrapper wrapper = JoinWrappers.lambda(UserDO.class) + .selectAll() + .selectCollection(AddressDO.class, UserDTO::getAddressList) + .leftJoin(AddressDO.class, on -> on + .eq(AddressDO::getUserId, UserDO::getId) + .ge(AddressDO::getUserId, 0)) + .le(UserDO::getId, 15) + .logicDelToOn() + .pageByMain(); + + Page page = wrapper.page(new Page<>(1, 8), UserDTO.class); + page.getRecords().forEach(System.out::println); + assert page.getRecords().size() == 8; + } + + @Test + void pageByMain1() { + ThreadLocalUtils.set("SELECT t.id, t.pid, t.`name`, t.`json`, t.sex, t.head_img, t.create_time, t.address_id, t.address_id2," + + " t.del, t.create_by, t.update_by, concat('?', t.id) AS sdafsdfsdfsd, t1.id AS joina_id, t1.user_id, " + + "t1.area_id, t1.tel, t1.address, t1.del AS joina_del FROM (SELECT * FROM `user` t WHERE t.del = false " + + "AND (t.id <= ?) LIMIT ?) t LEFT JOIN address t1 ON (t1.user_id = t.id AND t1.user_id >= ? AND t1.del = false)"); + MPJLambdaWrapper wrapper = JoinWrappers.lambda(UserDO.class) + .selectAll() + .selectFunc(() -> "concat('?',%s)", UserDO::getId, "sdafsdfsdfsd") + .selectCollection(AddressDO.class, UserDTO::getAddressList) + .leftJoin(AddressDO.class, on -> on + .eq(AddressDO::getUserId, UserDO::getId) + .ge(AddressDO::getUserId, 0)) + .le(UserDO::getId, 15) + .logicDelToOn() + .pageByMain(); + + Page page = wrapper.page(new Page<>(1, 8), UserDTO.class); + page.getRecords().forEach(System.out::println); + assert page.getRecords().size() == 8; + + } + + @Test + void test11() { + ThreadLocalUtils.set("SELECT t.id, t.pid, t.`name`, t.`json`, t.sex, t.head_img, t.create_time, t.address_id, " + + "t.address_id2, t.del, t.create_by, t.update_by, concat('?', t.id) AS sdafsdfsdfsd, t1.id AS joina_id, " + + "t1.user_id, t1.area_id, t1.tel, t1.address, t1.del AS joina_del FROM (SELECT * FROM `user` t " + + "WHERE t.del = false AND (t.id <= ?) GROUP BY t.id ORDER BY t.id DESC LIMIT ?) t LEFT JOIN address t1 ON " + + "(t1.user_id = t.id AND t1.user_id >= ? AND t1.del = false)"); + MPJLambdaWrapper wrapper = JoinWrappers.lambda(UserDO.class) + .selectAll() + .selectFunc(() -> "concat('?',%s)", UserDO::getId, "sdafsdfsdfsd") + .selectCollection(AddressDO.class, UserDTO::getAddressList) + .leftJoin(AddressDO.class, on -> on + .eq(AddressDO::getUserId, UserDO::getId) + .ge(AddressDO::getUserId, 0)) + .le(UserDO::getId, 15) + .orderByDesc(UserDO::getId) + .groupBy(UserDO::getId) + .logicDelToOn() + .pageByMain(f -> f.countSelectSql("1")); + + Page page = wrapper.page(new Page<>(1, 8), UserDTO.class); + page.getRecords().forEach(System.out::println); + assert page.getRecords().size() == 8; + } + + @Test + void test13() { + ThreadLocalUtils.set("SELECT t.id, t.pid, t.`name`, t.`json`, t.sex, t.head_img, t.create_time, t.address_id, " + + "t.address_id2, t.del, t.create_by, t.update_by, concat('?', t.id) AS sdafsdfsdfsd, t1.id AS joina_id, " + + "t1.user_id, t1.area_id, t1.tel, t1.address, t1.del AS joina_del FROM (SELECT * FROM `user` t " + + "WHERE t.del = false AND (t.id <= ?) GROUP BY t.id ORDER BY t.id DESC LIMIT ?) t LEFT JOIN address t1 ON " + + "(t1.user_id = t.id AND t1.user_id >= ? AND t1.del = false)"); + MPJLambdaWrapper wrapper = JoinWrappers.lambda(UserDO.class) + .selectAll() + .selectFunc(() -> "concat('?',%s)", UserDO::getId, "sdafsdfsdfsd") + .selectCollection(AddressDO.class, UserDTO::getAddressList) + .leftJoin(AddressDO.class, on -> on + .eq(AddressDO::getUserId, UserDO::getId) + .ge(AddressDO::getUserId, 0)) + .le(UserDO::getId, 15) + .orderByDesc(UserDO::getId) + .groupBy(UserDO::getId) + .logicDelToOn() + .pageByMain(); + + Page page = wrapper.page(new Page<>(1, 8), UserDTO.class); + page.getRecords().forEach(System.out::println); + assert page.getRecords().size() == 8; + } +}