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分页查询的步骤。

  1. 编写一个查询sql
  2. 编写一个分页查询方法,设置PageHelper的当前页和页大小
  3. 执行查询语句
  4. 查询完成后把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集成

相关推荐