数据分页 | LIXI.FUN
0%

数据分页

比较朴素的做法

前端发送请求分页的必要数据

1
2
3
4
{
pageNum: 1,
pageSize: 10,
}

后端接收这几个参数

1
2
3
4
5
6
public class Param {
// 其他参数
// ...
private Integer pageNum;
private Integer pageSize;
}

这里有什么问题呢?

  1. 每个 需要分页操作的参数,需要有 pageNum pageSize 这些参数;
  2. 每个 需要分页的操作,需要在 sql 中手动显式拼接 LIMIT 之类的分页操作。

参数类里的 page 相关参数拆分

比较朴素的做法是把分页的数据抽出来一个父类,用 Param 继承 BasePage 类,如下:

1
2
3
4
5
6
7
8
9
public class BasePage {
private Integer pageNum;
private Integer pageSize;
}

public class Param extends BasePage {
// 业务参数
// ...
}

这样只是把写在 Param 里的分页参数,换了个地方,实际还是对于业务的一个强侵入,比如当 Param 需要继承一个比 BasePage 重要程度要高的业务相关类的时候,因为 Java 的单继承机制,分页参数的去向又需要某些迂回操作了。

另一种方式是直接从 HttpRequest 里往外拿值,让业务的 Param 对分页操作无感知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pubic class Param { // 没有继承 BasePage
// 业务参数
// ...
}

public calss Page {
private Integer pageNum;
private Integer pageSize;
}

public static Page getPage(HttpRequest request) {
Integer pageNum = request.getParam("pageNum");
Integer pageSize = request.getParam("pageSize");

return new Page(pageNum, pageSize);
}

看到这里,业务参数和分页参数就已经分开了,但还没显现出优势,比如你可能会问,这样往后传递参数的时候,不还多了一个 Page 类型的参数么?从参数的强耦合,变成了接口传参的强耦合了。

1
2
3
4
5
// 原有的
List<Result> selectList(Param param);

// 改完的
List<Result> selectList(Param param, Page page);

这样不是更大的麻烦了吗?

解决一个问题,引入另一个问题,扯淡了。。。

莫慌,有解决方法,ThreadLocal 线程共享变量,从 HttpRequest 中拿到 Page 相关的参数之后,不往后传,而是存到 ThreadLocal 中,当有需要的时候再去拿,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class PageHolder {
private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

public static void setPage(Page page) {
LOCAL_PAGE.set(page);
}

public static Page getPage() {
return LOCAL_PAGE.get();
}

public static void clearPage() {
LOCAL_PAGE.remove();
}
}

// 保持原有接口不变
List<Result> selectList(Param param);

那什么时候需要去拿那个参数呢?执行 SQL 之前。

拼接 SQL

PageHolder 中拿到 Page 相关的参数之后,根据具体的数据库类型,在 SQL 上拼接上分页需要的操作,比如 mysql 的 limit

这里面需要解决什么问题呢?

如果自己写 sql 的时候不去写 limit 而让程序自己去拼接,则需要 拦截 执行 sql 的操作(比如这里的 selectList(Param param))拿到要执行的 sql 语句,怎么做呢?

在 Mybatis 中的 org.apache.ibatis.plugin 中的 InterceptsInterceptor 允许定义自己的拦截器,在自定义实现中,就可以在 sql 上拼接上 limit 并把 PageHolder 中的 Page 相关参数设置进去。

至此就可以做到业务无侵入的分页了。

注意: ThreadLocal 的值用完以后,记得在 finally 块remove(),避免内存泄漏。

总结

  1. 从 HttpReuqest 中直接拿 Page 相关参数
  2. 使用 ThreadLocal 存储 Page 参数
  3. 自定义拦截器,拼接 limit + Page 相关参数

原理大概就是这样了,而且也不需要自己再去造轮子了,有成熟的库可用

MyBatis 分页插件 PageHelper

觉得有收获就鼓励下作者吧