这是旧版本的做法以及一些当时遇到的问题《支持连表等复杂查询的PageHelper修改》
不想看理论分析的可以点击这里直接移步: 六、改造步骤详解
一、PageHelper中分页存在的问题:
- 主表结构如下:
provider_order表,订单主表。我们这里简化处理,它只有一个字段,即id。
VARCHAR | po_id | 订单表id
- 从表结构如下:
order_info表,订单从表。它负责记录订单的一些信息,与主表是一对多的关系。
VARCHAR | oi_id | 订单信息id
VARCHAR | po_id | 订单表id
TEXT | info | 信息详情
- 现在我们需要一个简单的连表查询操作,sql语句如下:
<select id="getProviderOrderWithOrderInfoTest" resultMap="ResultMapWithOrderInfo" parameterType="java.lang.String">
SELECT *
FROM (SELECT * FROM provider_order WHERE po_id LIKE #{po_id}) AS limitable
LEFT JOIN order_info ON limitable.po_id = order_info.provider_order_id
</select>
- 进行PageHelper的分页操作,查询前十条记录:
Page page = PageHelper.startPage(0, 10);
List<ProviderOrder> providerOrderList = testService.getProviderOrderWithOrderInfoTest("%-%");
PageInfo<ProviderOrder> pageInfo = new PageInfo<>(providerOrderList);
System.out.println(pageInfo);
- 目前我们的主表有两条记录,从表有二十条记录。
我们会发现,查询出来的总数是20,然后查询结果是两个list(各5条从表信息),不仅如此,分页数据也很奇怪,我们只有两条主表记录,但却被分成了两页(pages = 2),并且hasNextPage = true。这并不是我们要的结果。我们想要查询的效果是:
前十条 订单 记录,然后连表查询出该十条订单记录下的所有 订单信息
也就是说,结果集中,首先我们应该得到两个list,list中各10条从表信息,而且也不该有下一页,总页数应为1,总数据数应为2。
查看PageHelper的源码我们来看看,它的count,以及limit,到底是怎么回事。
二、 PageHelper的count实现
private Long executeAutoCount(Executor executor, MappedStatement countMs, Object parameter, BoundSql boundSql, RowBounds rowBounds, ResultHandler resultHandler) throws IllegalAccessException,
SQLException {
Map<String, Object> additionalParameters = (Map) this.additionalParametersField.get(boundSql);
CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
String countSql = this.dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
Iterator var11 = additionalParameters.keySet()
.iterator();
while (var11.hasNext()) {
String key = (String) var11.next();
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
Long count = (Long) ((List) countResultList).get(0);
// 获取count
return count;
}
我们很容易看出来,count的sql语句,在这里经过了处理,这里调用了dialect的getCountSql方法。
它的实现如下:
public String getSmartCountSql(String sql, String name) {
Statement stmt = null;
if (sql.indexOf("/*keep orderby*/") >= 0) {
return this.getSimpleCountSql(sql);
} else {
try {
stmt = CCJSqlParserUtil.parse(sql);
} catch (Throwable var8) {
return this.getSimpleCountSql(sql);
}
Select select = (Select)stmt;
SelectBody selectBody = select.getSelectBody();
try {
this.processSelectBody(selectBody);
} catch (Exception var7) {
return this.getSimpleCountSql(sql);
}
this.processWithItemsList(select.getWithItemsList());
this.sqlToCount(select, name);
String result = select.toString();
return result;
}
}
public String getSimpleCountSql(String sql) {
return this.getSimpleCountSql(sql, "0");
}
public String getSimpleCountSql(String sql, String name) {
StringBuilder stringBuilder = new StringBuilder(sql.length() + 40);
stringBuilder.append("select count(");
stringBuilder.append(name);
stringBuilder.append(") from (");
stringBuilder.append(sql);
stringBuilder.append(") tmp_count");
return stringBuilder.toString();
}
简单来说,就是在sql外面套一层select count语句,我们的sql语句变成了 SELECT count(0) FROM (我们的sql语句)(真实情况没有这么简单,这只是其中一种情况。)这样就很容易得知,为什么最后的total是20(所有数据数),而不是2(主表数据数)了。
三、 PageHelper的分页实现
与上面相同,这里以dialect(为了适配各种数据库)为mysql为例,我们可以看到,如果你没有在sql语句里面写limit,就会在sql语句的最后面,添加limit语句。
public String getPageSql(String sql, RowBounds rowBounds, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (rowBounds.getOffset() == 0) {
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(rowBounds.getLimit());
} else {
sqlBuilder.append(" LIMIT ");
sqlBuilder.append(rowBounds.getOffset());
sqlBuilder.append(",");
sqlBuilder.append(rowBounds.getLimit());
pageKey.update(rowBounds.getOffset());
}
pageKey.update(rowBounds.getLimit());
return sqlBuilder.toString();
}
程序运行得到sql语句如下,PageHelper对查询的总结果集(包括主表数据与从表数据),进行了分页
在得知PageHelper在这两个步骤的原理后,我们开始对PageHelper进行改造。
四、 count结果修正
在上面的count源码中,我们可以看到这么一段代码:
public String getSmartCountSql(String sql, String name) {
Statement stmt = null;
if (sql.indexOf("/*keep orderby*/") >= 0) {
return this.getSimpleCountSql(sql);
} else {
try {
stmt = CCJSqlParserUtil.parse(sql);
} catch (Throwable var8) {
return this.getSimpleCountSql(sql);
}
Select select = (Select)stmt;
SelectBody selectBody = select.getSelectBody();