比较朴素的做法
前端发送请求分页的必要数据
{
pageNum: 1,
pageSize: 10,
}
后端接收这几个参数
public class Param {
// 其他参数
// ...
private Integer pageNum;
private Integer pageSize;
}
这里有什么问题呢?
- 对 每个 需要分页操作的参数,需要有
pageNum
pageSize
这些参数; - 对 每个 需要分页的操作,需要在 sql 中手动显式拼接
LIMIT
之类的分页操作。
参数类里的 page 相关参数拆分
比较朴素的做法是把分页的数据抽出来一个父类,用 Param
继承 BasePage
类,如下:
public class BasePage {
private Integer pageNum;
private Integer pageSize;
}
public class Param extends BasePage {
// 业务参数
// ...
}
这样只是把写在 Param
里的分页参数,换了个地方,实际还是对于业务的一个强侵入,比如当 Param
需要继承一个比 BasePage
重要程度要高的业务相关类的时候,因为 Java 的单继承机制,分页参数的去向又需要某些迂回操作了。
另一种方式是直接从 HttpRequest
里往外拿值,让业务的 Param
对分页操作无感知。
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
类型的参数么?从参数的强耦合,变成了接口传参的强耦合了。
// 原有的
List<Result> selectList(Param param);
// 改完的
List<Result> selectList(Param param, Page page);
这样不是更大的麻烦了吗?
解决一个问题,引入另一个问题,扯淡了。。。
莫慌,有解决方法,ThreadLocal
线程共享变量,从 HttpRequest
中拿到 Page
相关的参数之后,不往后传,而是存到 ThreadLocal
中,当有需要的时候再去拿,如下:
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
中的 Intercepts
和 Interceptor
允许定义自己的拦截器,在自定义实现中,就可以在 sql 上拼接上 limit
并把 PageHolder
中的 Page
相关参数设置进去。
至此就可以做到业务无侵入的分页了。
注意: ThreadLocal 的值用完以后,记得在 finally 块
里 remove()
,避免内存泄漏。
总结
- 从 HttpReuqest 中直接拿 Page 相关参数
- 使用 ThreadLocal 存储 Page 参数
- 自定义拦截器,拼接 limit + Page 相关参数
原理大概就是这样了,而且也不需要自己再去造轮子了,有成熟的库可用