Spring使用AOP实现PageHelper分页

本文介绍使用AOP实现分页功能,通过PageHelper插件和SpringBoot框架,实现代码零入侵,有效减少代码冗余,提升开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AOP实现分页有什么好处

利用AOP实现分页功能可以达到零代码入侵的目的,只需要在请求方法上传入对应的分页请求数据即可,SQL的编写以及后台业务与分页代码无关。

PageHelper

PageHelper是Mybatis的一款分页插件,利用ThreadLocal实现分页功能。PageHelper先是根据你即将发出的SQL命令获取count值(也就是数据总量),然后获取当前线程上的线程变量进行分页操作。

执行流程

  1. 使用aop获取controller方法中分页的相关请求数据PageBean
  2. 将PageBean保存在线程变量中
  3. 使用aop拦截dao方法,调用PageHelper的分页方法(使用两次aop是因为PageHelper只会对即将执行的SQL语句进行分页,假设你要分页的数据是第二顺序执行,这时候则会导致第一顺序执行的SQL语句被分页,而第二顺序执行的SQL语句相反)

说一下为什么要使用AOP进行分页实现,先看代码

    @Override
    public PageInfo<User> selectAllUser(Integer currentPage, Integer pageSize) {
        PageHelper.startPage(currentPage,pageSize);
        List<User> userList = userMapper.selectPage();
        PageInfo<User> userPageInfo = new PageInfo<>(userList);
        return userPageInfo;
    }

       在查询语句的上下都需要引用相应的PageHelper方法,当方法较少的时候没什么,但如果有好几百个方法都是这样的,那么就会显得代码冗余,我们就可以通过AOP思想将其提取出来,减少代码量。

我是使用SpringBoot进行的代码测试,目录结构如下:

pom.xml文件所引用的jar包:

    <dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<!-- springboot 整合 pagehelper -->
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<version>1.2.5</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.7</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

	</dependencies>

application.yml文件配置:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123

pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params:
    count: countSql
  page-size-zero: true

注意:我用的SpringBoot版本为2.0.x,在引用数据源的时候用driver-class-name,

           如果是1.5.x版本的话,需要是driverClassName

实体类:

@Data
public class User {
    private Integer id;
    private String name;
    private String code;
    private Long password;
    private boolean status;
}

分页工具类:

@Data
public class PageBean<T> {
    // 当前页
    private Integer currentPage;
    // 每页显示的总条数
    private Integer pageSize;
    // 总条数
    private Integer totalNum;
    // 是否有下一页
    private Integer isMore;
    // 总页数
    private Integer totalPage;
    // 开始索引
    private Integer startIndex;
    // 分页结果
    private List<T> items;
}

Mapper接口层:

public interface UserMapper {
    @Select("select * from user")
    List<User> selectPage();

    @Select("select * from user")
    List<User> selectWithPage();
}

注意:此处之所以没有写@Mapper注解是因为,我已经在SpringBoot启动类中已经加上

@MapperScan(basePackages = {"com.zrl.mapper"})进行声明了

编写Controller

Controller方法中使用了PageBean接收分页参数或者使用restful风格接收参数,这里并没有限制你必须这么接收,不过你怎么接收分页参数的就需要在aop中怎么拦截并获取,我这里是使用实体类进行接收:

@RestController
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/select")
    public List<User> selectAll(){
        return userService.selectAll();
    }

    @RequestMapping("/selectAllUser/{currentPage}/{pageSize}")
    public PageInfo<User> selectAllUser(@PathVariable("currentPage") Integer currentPage, @PathVariable("pageSize") Integer pageSize){
        return userService.selectAllUser(currentPage,pageSize);
    }

    /**
     * 必须要用这两个注解 @PostMapping  @RequestBody
     */
    @PostMapping("/selectAllUserWithPage")
    public PageInfo<User> selectAllUserWithPage(@RequestBody PageBean pageBean){
        return userService.selectAllUserWithPage();
    }
}

service层实现类:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> selectAll() {
        return userMapper.selectPage();
    }

    @Override
    public PageInfo<User> selectAllUser(Integer currentPage, Integer pageSize) {
        PageHelper.startPage(currentPage,pageSize);
        List<User> userList = userMapper.selectPage();
        PageInfo<User> userPageInfo = new PageInfo<>(userList);
        return userPageInfo;
    }

    /**
     * 引用PageHelper.startPage(currentPage,pageSize);这一步,通过AOP前置通知完成,节省代码
     * new PageInfo<>(userList)  这一步理论上也应该能够实现,但我没想到怎么实现,有思路的大神提一下意见 
     * @return
     */
    @Override
    public PageInfo<User> selectAllUserWithPage() {
        return new PageInfo<>(userMapper.selectWithPage());
    }
}

编写PageHelperAOP类

@Component
@Aspect
public class PageHelperAop {
    private static final Logger log = LoggerFactory.getLogger(PageHelperAop.class);
    //使用线程本地变量
    private static final ThreadLocal<PageBean> pageBeanContext = new ThreadLocal<>();

    //以WithPage结尾的Controller方法都是需要分页的方法
    @Before(value = "execution(public * com.zrl.controller.*.*WithPage(..))")
    public void controllerAop(JoinPoint joinPoint) throws Exception {
        log.info("Controller正在执行PageHelperAop");
        PageBean pageBean =null;

        Object[] args = joinPoint.getArgs();
        //获取类名
        String clazzName = joinPoint.getTarget().getClass().getName();
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        //通过反射获取参数列表
        Map<String,Object > nameAndArgs = this.getFieldsName(this.getClass(), clazzName, methodName,args);

        pageBean = (PageBean) nameAndArgs.get("pageBean");
        if(pageBean == null){
            pageBean = new PageBean();
            pageBean.setCurrentPage((Integer) nameAndArgs.get("currentPage"));
            pageBean.setPageSize((Integer) nameAndArgs.get("pageSize"));
        }
        //将分页参数放置线程变量中
        pageBeanContext.set(pageBean);
    }

    @Before(value = "execution(public * com.zrl.service.impl.*.*WithPage(..))")
    public void serviceImplAop(){
        log.info("Impl正在执行PageHelperAop");
        PageBean pageBean = pageBeanContext.get();
        PageHelper.startPage(pageBean.getCurrentPage(), pageBean.getPageSize());
    }

    @AfterReturning(value = "execution(* com.zrl.mapper.*.*WithPage(..))")
    public void mapperAop(){
        log.info("mapper正在执行PageHelperAop");
    }

    /**
     * 通过反射获取参数列表
     * @throws Exception
     */
    private Map<String,Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws Exception {
        Map<String,Object > map=new HashMap<String,Object>();

        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++){
            map.put( attr.variableName(i + pos),args[i]);
        }
        return map;
    }
}

测试:

应用Postman软件进行模拟请求:

查询结果:

日志打印:

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值