mirror of
https://gitee.com/best_handsome/mybatis-plus-join
synced 2025-07-11 00:02:22 +08:00
优化分页
This commit is contained in:
parent
064f5ccaa9
commit
69713b1c39
@ -75,4 +75,7 @@ public interface IAdapter {
|
||||
}
|
||||
return typeHandler;
|
||||
}
|
||||
|
||||
default void checkCollectionPage() {
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -34,7 +34,6 @@ public class MPJInterceptorConfig {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void replaceInterceptorChain(List<SqlSessionFactory> 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<Interceptor> list = (List<Interceptor>) interceptors.get(chain);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
interceptors.set(chain, new InterceptorList<>());
|
||||
|
@ -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<T, Children extends AptAbstractWrapper<
|
||||
protected boolean resultMap = false;
|
||||
@Getter
|
||||
protected boolean resultMapCollection = false;
|
||||
|
||||
@Setter
|
||||
protected PageInfo pageInfo;
|
||||
|
||||
@Getter
|
||||
protected boolean pageByMain = false;
|
||||
/**
|
||||
* 表序号
|
||||
*/
|
||||
@ -134,6 +142,29 @@ public abstract class AptAbstractWrapper<T, Children extends AptAbstractWrapper<
|
||||
initNeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主表分页
|
||||
*/
|
||||
public Children pageByMain() {
|
||||
this.pageByMain = true;
|
||||
this.pageInfo = new PageInfo();
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主表分页
|
||||
*/
|
||||
public Children pageByMain(MFunction<PageInfo.PageInfoBuilder> 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<T, Children extends AptAbstractWrapper<
|
||||
this.alias = ConfigProperties.tableAlias;
|
||||
this.resultMap = false;
|
||||
this.resultMapCollection = false;
|
||||
this.pageInfo = null;
|
||||
this.pageByMain = false;
|
||||
this.tableIndex = 1;
|
||||
this.dynamicTableName = false;
|
||||
this.tableFunc = null;
|
||||
|
@ -14,9 +14,12 @@ import com.github.yulichang.extension.kt.interfaces.QueryJoin;
|
||||
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.segments.PageInfo;
|
||||
import com.github.yulichang.wrapper.segments.SelectCache;
|
||||
import kotlin.reflect.KProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
@ -57,6 +60,12 @@ public abstract class KtAbstractLambdaWrapper<T, Children extends KtAbstractLamb
|
||||
protected boolean resultMap = false;
|
||||
@Getter
|
||||
protected boolean resultMapCollection = false;
|
||||
|
||||
@Setter
|
||||
protected PageInfo pageInfo;
|
||||
|
||||
@Getter
|
||||
protected boolean pageByMain = false;
|
||||
/**
|
||||
* 表序号
|
||||
*/
|
||||
@ -168,6 +177,29 @@ public abstract class KtAbstractLambdaWrapper<T, Children extends KtAbstractLamb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主表分页
|
||||
*/
|
||||
public Children pageByMain() {
|
||||
this.pageByMain = true;
|
||||
this.pageInfo = new PageInfo();
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主表分页
|
||||
*/
|
||||
public Children pageByMain(MFunction<PageInfo.PageInfoBuilder> 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<T, Children extends KtAbstractLamb
|
||||
this.alias = ConfigProperties.tableAlias;
|
||||
this.resultMap = false;
|
||||
this.resultMapCollection = false;
|
||||
this.pageInfo = null;
|
||||
this.pageByMain = false;
|
||||
this.tableIndex = 1;
|
||||
this.dynamicTableName = false;
|
||||
this.tableFunc = null;
|
||||
|
@ -1,15 +1,14 @@
|
||||
package com.github.yulichang.interceptor;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisPlusVersion;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.*;
|
||||
import com.github.yulichang.adapter.AdapterHelper;
|
||||
import com.github.yulichang.adapter.base.tookit.VersionUtils;
|
||||
import com.github.yulichang.config.ConfigProperties;
|
||||
import com.github.yulichang.interfaces.MPJBaseJoin;
|
||||
import com.github.yulichang.toolkit.ReflectionKit;
|
||||
import com.github.yulichang.toolkit.*;
|
||||
import com.github.yulichang.toolkit.support.FieldCache;
|
||||
import com.github.yulichang.wrapper.interfaces.SelectWrapper;
|
||||
@ -20,6 +19,8 @@ import com.github.yulichang.wrapper.segments.SelectLabel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.executor.Executor;
|
||||
import org.apache.ibatis.logging.Log;
|
||||
import org.apache.ibatis.logging.LogFactory;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.ResultFlag;
|
||||
import org.apache.ibatis.mapping.ResultMap;
|
||||
@ -50,6 +51,7 @@ public class MPJInterceptor implements Interceptor {
|
||||
private static final Map<String, Val> MS_MAPPER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, Val> 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 <E> MappedStatement getMappedStatement(MappedStatement ms, Class<?> resultType, Object ew) {
|
||||
public <E> MappedStatement getMappedStatement(MappedStatement ms, Class<?> resultType, Object ew, Map<String, Object> map) {
|
||||
if (ew instanceof SelectWrapper) {
|
||||
SelectWrapper<E, ?> wrapper = (SelectWrapper<E, ?>) 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);
|
||||
}
|
||||
|
@ -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<ParameterMapping> fullMappings = new ArrayList<>();
|
||||
@Getter
|
||||
private final List<ParameterMapping> pageMappings = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
private String finallySql;
|
||||
|
||||
public DialectWrapper(IDialect dialect) {
|
||||
this.dialect = dialect;
|
||||
}
|
||||
|
||||
public void buildPaginationSql(String originalSql, List<ParameterMapping> mappings, long offset, long limit,
|
||||
Consumer<DialectModel> 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<Join> 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<Integer, ParameterMapping> 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<Integer, ParameterMapping> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<ParameterMapping> 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<Object> 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<OrderItem> 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<String, Object> 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<ParameterMapping> 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<OrderByElement> 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<Join> 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<Integer, ParameterMapping> 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<SelectWrapper<?, ?>> 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();
|
||||
}
|
||||
|
||||
}
|
@ -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<SqlSource, SqlSourceWrapper> 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<Object, String> 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<Object, String> formatter) {
|
||||
return decode(str, formatter, placeholder_char);
|
||||
}
|
||||
|
||||
public static String decode(String str, Function<Object, String> 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<Object, String> 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<ParameterMapping> mappings, int count, String repSql, Map<Integer, ParameterMapping> sortMap) {
|
||||
return encode(mappings, count, repSql, sortMap, placeholder_str);
|
||||
}
|
||||
|
||||
public static String encode(List<ParameterMapping> mappings, int count, String repSql, Map<Integer, ParameterMapping> 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(""));
|
||||
}
|
||||
}
|
@ -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<String, Object> 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<ParameterMapping> parameterMappings = new ArrayList<>();
|
||||
private final Class<?> parameterType;
|
||||
private final MetaObject metaParameters;
|
||||
|
||||
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,
|
||||
Map<String, Object> additionalParameters) {
|
||||
super(configuration);
|
||||
this.parameterType = parameterType;
|
||||
this.metaParameters = configuration.newMetaObject(additionalParameters);
|
||||
}
|
||||
|
||||
public List<ParameterMapping> getParameterMappings() {
|
||||
return parameterMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handleToken(String content) {
|
||||
parameterMappings.add(buildParameterMapping(content));
|
||||
return "?";
|
||||
}
|
||||
|
||||
private ParameterMapping buildParameterMapping(String content) {
|
||||
Map<String, String> 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<String, String> 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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<E extends Interceptor> extends ArrayList<E> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(InterceptorList.class);
|
||||
|
||||
public InterceptorList() {
|
||||
super();
|
||||
}
|
||||
@ -29,6 +40,41 @@ public class InterceptorList<E extends Interceptor> extends ArrayList<E> {
|
||||
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<InnerInterceptor> interceptors = (List<InnerInterceptor>) 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
|
||||
|
@ -71,16 +71,13 @@ public final class ReflectionKit {
|
||||
* @param fieldName 字段名称
|
||||
* @return 属性值
|
||||
*/
|
||||
public static Object getFieldValue(Object entity, String fieldName) {
|
||||
Class<?> cls = entity.getClass();
|
||||
Map<String, Field> fieldMaps = getFieldMap(cls);
|
||||
public static <T> 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<T, Children extends JoinAbstract
|
||||
*/
|
||||
@Getter
|
||||
protected boolean resultMapCollection = false;
|
||||
|
||||
@Setter
|
||||
protected PageInfo pageInfo;
|
||||
|
||||
@Getter
|
||||
protected boolean pageByMain = false;
|
||||
/**
|
||||
* 表序号
|
||||
*/
|
||||
@ -168,6 +177,29 @@ public abstract class JoinAbstractLambdaWrapper<T, Children extends JoinAbstract
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主表分页
|
||||
*/
|
||||
public Children pageByMain() {
|
||||
this.pageByMain = true;
|
||||
this.pageInfo = new PageInfo();
|
||||
return typedThis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主表分页
|
||||
*/
|
||||
public Children pageByMain(MFunction<PageInfo.PageInfoBuilder> 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<T, Children extends JoinAbstract
|
||||
this.alias = ConfigProperties.tableAlias;
|
||||
this.resultMap = false;
|
||||
this.resultMapCollection = false;
|
||||
this.pageInfo = null;
|
||||
this.pageByMain = false;
|
||||
this.tableIndex = 1;
|
||||
this.dynamicTableName = false;
|
||||
this.tableFunc = null;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.github.yulichang.wrapper.interfaces;
|
||||
|
||||
import com.github.yulichang.wrapper.resultmap.Label;
|
||||
import com.github.yulichang.wrapper.segments.PageInfo;
|
||||
import com.github.yulichang.wrapper.segments.Select;
|
||||
|
||||
import java.util.List;
|
||||
@ -26,4 +27,10 @@ public interface SelectWrapper<Entity, Children> {
|
||||
String getFrom();
|
||||
|
||||
String getAlias();
|
||||
|
||||
boolean isResultMapCollection();
|
||||
|
||||
PageInfo getPageInfo();
|
||||
|
||||
boolean isPageByMain();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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<UserDO> 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<UserDTO> 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<UserDO> 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<UserDTO> 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<UserDO> 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<UserDTO> 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<UserDO> 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<UserDTO> page = wrapper.page(new Page<>(1, 8), UserDTO.class);
|
||||
page.getRecords().forEach(System.out::println);
|
||||
assert page.getRecords().size() == 8;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user