**高并发、微服务 、性能调优实战案例100讲，所有案例均源于个人工作实战，均配合代码落地**

加我微信：itsoku，所有案例均提供在线答疑。

# 第25节 使用AOP简化MyBatis分页功能

<span style="font-weight:bold; color:red">目前整个课程59块钱，100个案例，含所有源码 & 文档 & 技术支持，可点击左下角小黄车了解</span>。

## 本文内容

- PageHelper实现MyBatis分页
- 使用AOP简化MyBatis分页



## 涉及到的技术

- SpringBoot 2.7.13
- MyBatis
- PageHelper：一个很好用的 MyBatis 分页工具
- AOP环绕通知



## 案例1：PageHelper实现Mybatis分页

### PageHelper是什么？

一个开源的mybatis分页工具，github地址：https://pagehelper.github.io/

### PageHelper原理

使用Mybatis的拦截器，对sql进行改写，进而实现分页功能。

### 如何使用？（3步）

引入对应的maven配置

```xml
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>
```

application.yml中添加

> 每种数据库分页语法不一样，需要指定咱们使用是那种数据库

```properties
pagehelper:
  helper-dialect: mysql # 数据方言
```

分页查询

```java
//开启分页
PageHelper.startPage(pageNum, pageSize, true);
try {
    //Mapper接口中任意查询，返回List的方法
} finally {
    //清理分页信息
    PageHelper.clearPage();
}
```

### 案例代码

```java
com.itsoku.lesson025.service.UserService#selectPage
    
public PageResult<UserPO> selectPage(int pageNum, int pageSize) {
    //开启分页
    PageHelper.startPage(pageNum, pageSize, true);
    try {
        List<UserPO> users = this.userMapper.selectPage();
        return PageResult.of(users);
    } finally {
        //清理分页信息
        PageHelper.clearPage();
    }
}
```

### 测试用例

```java
http://localhost:8080/user/selectPage?pageNum=2&pageSize=10
```

### 存在问题

上面的案例有个问题，代码如下，就是每次写的时候，都需要PageHelper.startPage，结束的时候，需要PageHelper.clearPage。

```java
//开启分页
PageHelper.startPage(pageNum, pageSize, true);
try {
   	//调用mapper中的方法分页查询数据
} finally {
    //清理分页信息
    PageHelper.clearPage();
}
```

### 如何解决这个问题？

我们可以通过aop搞个环绕通知，将这部分公共的代码丢到aop中，这样，就可以大大简化这个操作。

下面的案例2，将实现这个套方案。



## 案例2：使用AOP简化案例1中的分页

### 先看效果，再说原理

java代码

```java
com.itsoku.lesson025.service.UserService#selectPageNew
```

测试用例地址

```http
http://localhost:8080/user/selectPageNew?pageNum=1&pageSize=10
```

### 如何实现的？

#### IPageQuery：分页顶层接口

我们定义了一个分页的顶层接口，如下，有3个方法，用于获取分页信息，当我们mapper中的方法中有这个类型的参数的时候，就会被aop统一处理，进行分页。

```java
public interface IPageQuery {
    /**
     * 页码
     *
     * @return
     */
    int getPageNum();

    /**
     * 每页大小
     *
     * @return
     */
    int getPageSize();

    /**
     * 是否需要分页
     *
     * @return
     */
    boolean count();
}
```

#### PageQuery：IPageQuery默认实现

```java
public class PageQuery implements IPageQuery {
    private int pageNum = 1;
    private int pageSize = 10;
    private boolean count = true;

    /**
     * 获取分页请求参数
     *
     * @param pageNum  页码
     * @param pageSize 每页大小
     * @param count    是否需要分页
     * @return
     */
    public static PageQuery of(int pageNum, int pageSize, boolean count) {
        return new PageQuery(pageNum, pageSize, count);
    }
    
}
```

#### IPageQuery这个接口如何使用呢？

咱们的Mapper接口中需要分页的方法，方法的参数中，有任意一个参数是 IPageQuery 类型，那么这个方便自动拥有了分页功能，这个是通过aop实现的，稍后会介绍这块aop的代码，比如下面这个方法，第一个参数是 IPageQuery 类型，那么这个方法就自动拥有了分页功能。

```java
@Select("select id,name from t_user_lesson025 order by id asc")
List<UserPO> selectPageNew(IPageQuery pageQuery);
```

#### 原理：通过AOP统一处理IPageQuery参数，进行分页

通过AOP统一处理IPageQuery参数，实现分页功能，如下我们搞了个环绕通知，会拦截Mapper中的所有方法，判断方法的参数是否有IPageQuery类型参数，如果有，则从 IPageQuery 中拿到分页的信息，然后调用 PageHelper.startPage 开启分页，方法执行完毕之后，然后调用 PageHelper.clearPage 清理分页。

```java
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class PageQueryAspect {

    /**
     * 拦截mapper中的素有方法
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.itsoku..*Mapper.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        boolean pageFlag = false;
        try {
            //遍历参数，参数中如果有 IPageQuery 类型的，则从 IPageQuery 取出分页信息，则使用 PageHelper 开启分页
            Object[] args = pjp.getArgs();
            for (Object arg : args) {
                if (arg instanceof IPageQuery) {
                    IPageQuery pageQuery = (IPageQuery) arg;
                    PageHelper.startPage(pageQuery.getPageNum(), pageQuery.getPageSize(), pageQuery.count());
                    pageFlag = true;
                    break;
                }
            }
            return pjp.proceed();
        } finally {
            if (pageFlag) {
                //清理分页信息
                PageHelper.clearPage();
            }
        }
    }
}
```



## 案例3：案例2拓展实战

分页查询用户信息，支持根据关键字模糊检索用户名。

```java
com.itsoku.lesson025.service.UserService#userPage
```

测试，检索用户名中包含路人的用户列表，浏览器中访问

```http
http://localhost:8080/user/userPage?pageNum=1&pageSize=10&keyword=路人
```



## 案例源码

源码同样是放在我的《高并发&微服务&性能调优实战案例100讲》的代码中（lesson025模块中），有兴趣的可以点击左下角的小黄车了解下，感谢大家的观看。



# 高并发 & 微服务 & 性能调优实战案例100讲

## 已更新 25 节课

<span style="font-weight:bold; color:red">目前整个课程59块钱，含所有源码 & 文档 & 技术支持，一杯咖啡的价格，还没下手的朋友，赶紧了，马上要涨价了</span>。

```java
1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
```



## 课程部分大纲，连载中。。。。

以下课程均来源于个人多年的实战，均提供原理讲解 && 源码落地

1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定系统操作日志
25. Aop简化MyBatis分页功能
26. SpringBoot读写分离实战
27. MQ专题：事务消息实战（防止消息丢失）
28. MQ专题：消息消息重试策略
29. MQ专题：消息幂等消费通用方案实战
30. MQ专题：延迟消息通用方案实战
31. MQ专题：顺序消息通用方案实战
32. MQ专题：消息积压问题
33. 分布式事务：事务消息实现事务最终一致性
34. 分布式事务：通用的TCC分布式事务生产级代码落地实战
35. 分布式锁案例实战
36. 微服务中如何传递上下文？实战
37. 微服务链路日志追踪实战（原理&代码落地）
38. SpringBoot实现租户数据隔离
39. MyBatis进阶：封装MyBatis，实现通用的无SQL版CRUD功能，架构师必备
40. MyBatis进阶：自己实现通用分表功能，架构师必备
41. MyBatis进阶：实现多租户隔离ORM框架
42. SpringBoot中实现自动监听PO的变化，自动生成表结构
43. 分布式专题：其他实战课程等
44. 性能调优：如何排查死锁？
45. 性能调优：如何排查内存溢出？
46. 性能调优：CPU被打满，如何排查？
47. 性能调优：生产代码没生效，如何定位？
48. 性能调优：接口太慢，如何定位？
49. 性能调优：如何查看生产上接口的入参和返回值？
50. 性能调优：远程debug
51. 生产上出现了各种故障，如何定位？
52. db和缓存一致性，常见的方案
53. Redis场景案例。。。
54. 系统资金账户设计案例（一些系统涉及到资金操作）
55. 其他等各种实战案例。。。