比较朴素的做法
前端发送请求分页的必要数据
1 | { |
后端接收这几个参数
1 | public class Param { |
这里有什么问题呢?
- 对 每个 需要分页操作的参数,需要有
pageNumpageSize这些参数; - 对 每个 需要分页的操作,需要在 sql 中手动显式拼接
LIMIT之类的分页操作。
参数类里的 page 相关参数拆分
比较朴素的做法是把分页的数据抽出来一个父类,用 Param 继承 BasePage 类,如下:
1 | public class BasePage { |
这样只是把写在 Param 里的分页参数,换了个地方,实际还是对于业务的一个强侵入,比如当 Param 需要继承一个比 BasePage 重要程度要高的业务相关类的时候,因为 Java 的单继承机制,分页参数的去向又需要某些迂回操作了。
另一种方式是直接从 HttpRequest 里往外拿值,让业务的 Param 对分页操作无感知。
1 | pubic class Param { // 没有继承 BasePage |
看到这里,业务参数和分页参数就已经分开了,但还没显现出优势,比如你可能会问,这样往后传递参数的时候,不还多了一个 Page 类型的参数么?从参数的强耦合,变成了接口传参的强耦合了。
1 | // 原有的 |
这样不是更大的麻烦了吗?
解决一个问题,引入另一个问题,扯淡了。。。
莫慌,有解决方法,ThreadLocal 线程共享变量,从 HttpRequest 中拿到 Page 相关的参数之后,不往后传,而是存到 ThreadLocal 中,当有需要的时候再去拿,如下:
1 | public class PageHolder { |
那什么时候需要去拿那个参数呢?执行 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 相关参数
原理大概就是这样了,而且也不需要自己再去造轮子了,有成熟的库可用