最近项目又用到了Mybaits。在Mybatis中分页是个比较头疼的事,因为需要我们每次都写重复的sql。好在我们有PageHelper这样的分页工具,它可以拦截你的sql,从而进行分页操作。
一、使用PageHelper分页和遇到的问题
首先我们引入maven依赖。
<pagehelper-version>1.2.5</pagehelper-version>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-version}</version>
</dependency>
然后假定你有个需要分页的查询方法selectList()
,已经定义到mapper中了。这个方法是不分页的,因此你不必写任何分页的语句。
在service
或者persistance
层,你需要定义一个分页查询的方法selectPage()
。
public PageBean<ReportTemplate> selectPage(PageBean<User> page) {
//PageHelper设置当前页和页大小
PageHelper.startPage(page.getPageNo(), page.getPageSize());
PageHelper.orderBy(page.getSortedField());
List<User> users= userMapper.selectPage(page.getKeyWords());
PageInfo<User> pageInfo = new PageInfo<>(users);
page.setCount(pageInfo.getTotal());
page.setList(pageInfo.getList());
return page;
}
这样我们就完成了PageHelper的分页。我们总结下使用PageHelper分页查询的步骤。
- 编写一个查询sql
- 编写一个分页查询方法,设置PageHelper的当前页和页大小
- 执行查询语句
- 查询完成后把PageInfo的数据填充到自定义的PageBean中
以上四个步骤我们可以看出,除了第三步是真正需要我们手动写sql的,其他的步骤都是重复的过程。一个合格的程序员一定不要写重复的代码,那么我们有没有什么办法能去掉重复的代码呢?
首先你可能想到把PageHelper的设置和自定义PageBean的数据填充分别封装成一个函数,然后每次调用两个方法就行了。虽然这样确实能减少一定的代码,但是仅仅是减少了部分代码,还是没达到我想的效果。我们想的是最后能像lombok那样简单易用,只需要一个注解就能省掉许多代码。那么我们应该如果做到呢?
二、使用Spring boot AOP和自定义注解
AOP的概念相信阅读本文的人应该都有所了解,但是真正用起来的不算多。我这里不想谈AOP的概念,因为你可以很容易地从百度/谷歌上找到相关的文章。我会在文章的最后放上我查阅过的文章链接,以供你参考。
AOP的作用就是无侵入地实现部分功能,例如日志记录,操作记录等。
首先引入Spring boot AOP依赖
<!-- 引入Spring boot AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后我们先不管AOP的事,我们先定义一个我们需要的注解。一般来说只使用AOP就可以无侵入地实现分页,但是麻烦的一点在于AOP中切点匹配规则的扩展性不够强。尤其是我们开发过程中添加新功能往往会新增一个package,这就可能导致AOP的失效。因此我们在此处使用自定义注解,这样对于任意我们想分页的方法,只需要在上面加一个注解就能实现分页,这样用起来更灵活一些。
自定义注解很简单,你无须写任何逻辑代码。我们可以如下定义一个注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnablePaging {
}
定义好注解之后仅仅意味着你能在方法上使用此注解了。当然,你可以通过类的操作获取到这个注解。但是这还无法实现分页操作。我们需要写AOP的相关代码来实现分页操作了。
package org.flow.approval.annotation;
import com.github.pagehelper.PageHelper;
import com.google.common.base.Strings;
import com.sino.common.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.flow.bean.PageBean;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @Description: Mybatis排序的切入点实现方法
* @Author: gaoyakang
* @Version: 1.0
* @Create Date Time: 2020-05-07 17:52
* @Update Date Time:
* @see EnablePaging
*/
@Aspect
@Slf4j
@Component
public class MybatisPageEvent {
/**
* 启用分页实现类
*
* @param point 切入点
* @return 切入点的执行结果
* @throws Throwable 可能出现的异常
*/
@Around("@annotation(EnablePaging)")
public Object doPaging(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
//获取第一个参数
Object obj = args[0];
//仅仅当第一个参数为PageBean的时候才执行分页
if (obj instanceof PageBean) {
PageBean pageBean = (PageBean) obj;
Integer pageNum = pageBean.getPageNo();
Integer pageSize = pageBean.getPageSize();
if (Objects.isNull(pageSize)) {
pageSize = 10;
}
if (Objects.isNull(pageNum)) {
pageNum = 1;
}
//使用PageHelper进行分页
PageHelper.startPage(pageNum, pageSize);
log.info("启动分页方法,当前分页页号为:{},页大小:{}", pageNum, pageSize);
}
return point.proceed(args);
}
}
这里的代码也很简单。但是你需要按照你的需要来写,我认为照搬代码毕竟不可取。因为在我的项目中,我定义了一个PageBean,用来保存分页的一些信息,如当前页、页大小、分页后的数据、总数等信息。因此在我的项目中所有分页的方法,第一个参数必须是PageBean。在你的项目中,可能你是直接传rawType参数到方法中的,这里需要你按照自己的需要进行处理。
因为这里是环绕通知,所以我们也可以对方法调用之后的结果进行处理。
//分页逻辑省略......
Object result=point.proceed(args);
if(resultinstanceof List) {
List objList = (List) result;
PageInfo pageInfo = new PageInfo<>(objList);
return pageInfo;
}
实现上述操作之后的分页方法就变得简单多了
@Override
@EnablePaging
public PageBean<User> selectPage(PageBean<User> page) {
List<User> list = userMapper.selectPageData(page.getKeyWords());
PageInfo<User> pageInfo = new PageInfo<>(list);
//我的项目中因为需要特殊的操作,因此没有使用后置通知处理
page.setCount((int) pageInfo.getTotal());
page.setList(pageInfo.getList());
return page;
}
三、需要注意的是......
这里我遇到的问题是,在一些情况下AOP会失效。例如我现在有ControllerA,ServiceB,ServiceB中的方法fun1和fun2。这个三个层次,A调用了fun1(),fun1()调用fun2()。这种情况下AOP失效了。但是直接调用fun2()可以完美分页。暂时我还没发现为何会出现这种问题,如果你有任何见解或者看法,可以在评论区留言。
参考文章
博客园:Spring AOP + PageHelper分页
博客园:Spring Boot:实现MyBatis分页
Java获取类方法上的注解
比较 Spring AOP 与 AspectJ
柠檬五个半:Spring AOP SpringBoot集成