Mybatis 使用Spring boot AOP +自定义注解+PageHelper实现分页
最近项目又用到了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集成