Mybatis 分页插件 进阶使用
2022-07-14 本文已影响0人
kacen
我们的创建顺序及简述
1.创建分页实体对
2.创建分页工具类对象
- 创建分页插件 PagePlugin.java 这个类主要是分页逻辑的具体实现
- 创建PageInterceptor分页拦截器类 PageInterceptor.java 这个类主要是在客户端传递分页信息时,进行拦截的作用。
- 创建 基于springmvc响应处理器 - 处理返回Result对象统一设置分页信息 PageResponseBodyAdvice
- 创建Mybatis相关的工具类 MybatisUtils.java
首先保证有以下依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
1. 创建分页实体对象
@Data
public class Page {
/**
* 当前页
*/
private Integer pageNum;
/**
* 每页显示的条数
*/
private Integer pageSize;
/**
* 总条数
*/
private Integer pageCount;
/**
* 总页码
*/
private Integer pageTotal;
}
2..创建分页工具类对象
public class ExamplePage {
public static ThreadLocal<Page> pageThreadLocal = new ThreadLocal<>();
/**
* 设置分页信息
*/
public static void setPage(Integer pageNum, Integer pageSize) {
Page page = new Page();
page.setPageNum(pageNum);
page.setPageSize(pageSize);
pageThreadLocal.set(page);
}
/**
* 获取分页信息
*/
public static Page getPage() {
return pageThreadLocal.get();
}
/**
* 清除TreadLocal
*/
public static void clear() {
pageThreadLocal.remove();
}
}
3. 创建分页插件 PagePlugin.java
这里我的话是实现了Interceptor来自定义mybatis分页插件。
因为我们需要通过Interceptor来重写相关方法。
@Intercepts的话是指定需要拦截的地方, 类StatementHandler, 方法 prepare, 参数args = {Connection.class, Integer.class}。
当我们在设置页码的时候,很多人会这样写。
我们会先用 pageTotal=PageCount/pageSize 这个来算总页码
pageNum < 1,我们会直接设置为1,
当pageNum > pageTotal总页码的时候,我们会直接设置会总页码。
但是这样写的话会有一个问题,就是PageCount和pageSize都为0的时候那就很尴尬了。
这样子的话会有一个问题
逻辑会先走pageTotal=PageCount/pageSize,并设置PageNum为1,
然后再走pageNum > pageTotal,那这里的pageNum是=1的,而pageTotal为0,最后pageNum又会设置成0.
然后往下走的话会走到我们都会用的分页算法
这里如果再往下走会是0-1=-1,然后报错。
sql += " limit " + (page.getPageNum() - 1 ) * page.getPageSize() + "," + page.getPageSize();
详细代码
这里是具体的步骤中的问题代码
if (page.getPageNum() > page.getPageTotal()) {
page.setPageNum(page.getPageTotal());
}
// 开始进行分页,这里用的是拼接
// 策略模式 - 根据不同的数据库,选择使用不同的sql分页模式
// limit 后面需要 (加页码-1)*页面大小, 页面大小(这是算法)
sql += " limit " + (page.getPageNum() - 1 ) * page.getPageSize() + "," + page.getPageSize();
log.debug("[page - plugin] 分页sql - {}", sql)
这个是PagePlugin整个类哈,还有问题代码相关的修改
@Intercepts(
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
)
@Slf4j
public class PagePlugin implements Interceptor {
/**
* 拦截Mybatis PrepareStatement
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取StatementHandler
// 这里获取到的将是RoutingStatement
StatementHandler statementHandler = (StatementHandler) MybatisUtils.getProxyObject(invocation.getTarget());
System.out.println("当前stament对象:"+ statementHandler.getClass().getSimpleName());
//获取boundSql,里面含有sql语句
//因为在进行sql语句执行前会通过boundSql进行绑定赋值,所以在这里先获取,以便在拦截并处理后做相应的替换动作
BoundSql boundSql = statementHandler.getBoundSql();
// 获取拦截的sql语句
String sql = statementHandler.getBoundSql().getSql().toLowerCase().replaceAll("\n", "");
// 判断当前是否为查询的sql,是才可能分页,不是的话就不分页
if (!sql.startsWith("select")){
//非查询语句
return invocation.proceed();
}
// 根据当前请求的上下文,获取Page分页对象,如果能获取到,说明需要分页,如果不需要获取,说明无须分页
// TreadLocal 主要做线程隔离
Page page = ExamplePage.getPage();
if (page == null) {
// 未获取到分页对象
return invocation.proceed();
}
// 开始分页
log.debug("[page - plugin] 开始分页 - {}", sql);
// 计算出总条数
Integer count = getCount(sql, invocation, statementHandler);
// 进行相关的参数设置
// 默认显示10条
if (page.getPageSize() == null) {
page.setPageSize(10);
}
// 设置总条数
page.setPageCount(count);
// 总条数%设置的页数大小,如果可以整除证明只显示一页,如果!=0则需要添加页数
page.setPageTotal(page.getPageCount() % page.getPageSize() == 0 ?
page.getPageCount() / page.getPageSize() :
page.getPageCount() / page.getPageSize() + 1
);
// 判断页码的临界点,如果<1的话就直接设置为1,大于页码数的话就直接设置为页码数
if (page.getPageNum() < 1) {
page.setPageNum(1);
}
if (page.getPageNum() > page.getPageTotal() && page.getPageTotal() > 0) {
page.setPageNum(page.getPageTotal());
}
// 开始进行分页,这里用的是拼接
// 策略模式 - 根据不同的数据库,选择使用不同的sql分页模式
// limit 后面需要 (加页码-1)*页面大小, 页面大小(这是算法)
sql += " limit " + (page.getPageNum() - 1 ) * page.getPageSize() + "," + page.getPageSize();
log.debug("[page - plugin] 分页sql - {}", sql);
// 开始执行分页sql
// 将修改后的sql替换原来的sql,然后放行
MetaObject metaObject = SystemMetaObject.forObject(boundSql);
metaObject.setValue("sql", sql);
log.debug("[page - plugin] 分页完成!");
//放行,因为已经修改好了,Mybatis会执行修改后的sql
return invocation.proceed();
}
/**
* 计算查询到总条数
* @return 返回总条数
*/
private Integer getCount(String sql, Invocation invocation, StatementHandler statementHandler) throws SQLException {
int count = 0;
// 获取相关参数
Connection connection = (Connection) invocation.getArgs()[0];
// *后面必须有 空格
String countSql = "select count(*) as count " + sql.substring(sql.indexOf("from"));
log.debug("[page - plugin] 生成记录总条数的sql语句 - {}", countSql);
// 执行当前的sql语句
ResultSet resultSet = null;
PreparedStatement preparedStatement = null;
try{
preparedStatement = connection.prepareStatement(countSql);
// 设置sql相关参数, Mybatis自带的StatamentHandler中页有parametersize这个方法来将参数进行设置,
// 所以只要直接调用其方法既可跟平时写的sql一样将参数传入
statementHandler.parameterize(preparedStatement);
// 执行sql
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
// 直接获取count结果
count = resultSet.getInt("count");
log.debug("[page - plugin] 获取查询的记录总条数 {}", count);
return count;
}
}catch (SQLException e) {
throw new RuntimeException(e);
} finally{
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
}
return count;
}
}
4. 创建PageInterceptor分页拦截器类 PageInterceptor.java
@Component
@Slf4j
public class PageInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String pageNumStr = request.getParameter("pageNum");
String pageSizeStr = request.getParameter("pageSize");
log.debug("[page interceptor] 分页拦截器设置pageNum:{},设置pageSize{}",pageNumStr,pageSizeStr);
if (pageNumStr != null && pageSizeStr != null) {
try {
int pageNum = Integer.parseInt(pageNumStr);
int pageSize = Integer.parseInt(pageSizeStr);
PbShopPage.setPage(pageNum,pageSize);
} catch (Throwable e) {
return true;
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocal
log.debug("[page interceptor] 分页拦截器后置处理,清除ThreadLocal");
PbShopPage.clear();
}
}
5. 创建 基于springmvc响应处理器 - 处理返回Result对象统一设置分页信息 PageResponseBodyAdvice
@Slf4j
@RestControllerAdvice
public class PageResponseBodyAdvice implements ResponseBodyAdvice<Result> {
/**
* 判断什么条件出发beforeBodyWrite方法
* @param returnType the return type
* @param converterType the selected converter type
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 判断类型是否是Result类型的
return returnType.getMethod().getReturnType() == Result.class;
}
@Override
public Result beforeBodyWrite(Result body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//获取Threadlocal中的page对象
Page page = PbShopPage.getPage();
if (page == null) {
return body;
}
log.debug("[page responseAdvice] 分页返回值处理器生效: {}",page);
//将分页信息放到Result实体类中一并返回给客户端
body.setPage(page);
return body;
}
}
6. 创建Mybatis相关的工具类 MybatisUtils.java
/**
* 直接获取到代理的内置对象
* @param target
* @return
*/
public static Object getProxyObject(Object target) {
MetaObject metaObject = SystemMetaObject.forObject(target);
while (metaObject.hasGetter("h")) {
// 说明当前是一个代理对象
// 获取代理对象中持有的对象
target = metaObject.getValue("h.target");
metaObject = SystemMetaObject.forObject(target);
}
return target;
}
/**
* 返回一个sql语句中 主标的from 的index的位置
* @param beginIndex 起始from位置
* @param sql 传入主sql语句
* @return
*/
public static Integer getMainFrom(int beginIndex, String sql) {
if (sql == null) {
return -1;
}
//从index开始处往后查找,获取from关键词的下表
int fromIndex = sql.indexOf(" from ", beginIndex);
if (fromIndex == -1) {
return -1;
}
String sqlSub = sql.substring(0, fromIndex);
System.out.println(sqlSub);
int count = 0;
//转换成char数组
char[] chars = sqlSub.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c == '(') {
count++;
}
if (c == ')') {
count--;
}
}
//判断计数器
if (count == 0) {
return fromIndex;
}else {
return getMainFrom(fromIndex + 6, sql);
}
}
public static void main(String[] args) {
String sql = "select asdf,asdf,(select a,(select as from sjs),(select 1,3 from b),a from A) from Order where id = (select id from t)";
System.out.println(getMainFrom(0, sql));
String s = "select count(*) as count" + sql.substring(getMainFrom(0, sql));
System.out.println(s);
}