【SpringBoot】MyBatis与MyBatis-Plus分页查询 & github中的PageHelper
作者:mmseoamin日期:2023-12-25

        笔者写这篇博客是因为近期遇到的关于两者之间的分页代码差距,其实之前也遇见过但是没有去整理这篇博客,但由于还是被困扰了小一会儿时间,所以还是需要加深记忆。其实会看前后端传参解决这个问题很快、不麻烦。关于这两个框架的分页代码问题主要就是在业务层和MyBatis的SQL问题。注意:这里我不展示前端接口,需要知道的是前端会传给后端当前页(page)以及每页条数(size)。后端根据两个参数去实现分页(limit)。这里最容易踩坑的一个点:在MyBatis的分页中,(当前页需要 - 1) * size传入#{page},而在MyBatis-Plus中的new Page(page,size),则不需要去做运算,在Plus已经做好了这一点。详情请看下面代码块。

MyBatis

        那么关于MyBatis这个半ORM框架来说,SQL还是需要自己去写在xml中的或者是在注解上实现。这里我就采用xml种实现的方式去实现啦。为了减少代码量(真实开发不能这样的哈),我就将所有业务代码都放到控制层中。

引入依赖



	org.mybatis.spring.boot
	mybatis-spring-boot-starter
	2.2.2



	mysql
	mysql-connector-java
	8.0.31



    com.github.pagehelper
    pagehelper-spring-boot-starter
    1.2.12

修改yml

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/table?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath:mapper/*.xml  # 对应xml的位置
  type-aliases-package: com.chf.entity  # 对应namespace的实体类包名
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 日志类型
    map-underscore-to-camel-case: true  # 字段与属性的驼峰规则
# MyBatis的分页插件 这是最重要的
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countsql

业务代码

@RestController
@RequestMapping("/emp")
public class EmpController{
   
    //@Autowired
    //private EmpService empService; 
    // 为了博客简化代码量 所以直接调用Dao层接口
    @Autowired
    private EmpMapper empMapper;
    /**
     * 分页查询
     * @param pageNum 当前页数
     * @param pageSize 每页条数
     * @return
     */
    @GetMapping("/findAll/{page}/{size}")
    public R findAll(@PathVariable("page") Integer pageNum, @PathVariable("size") Integer pageSize){
        Integer page = (pageNum - 1) * pageSize;
        List empList = empMapper.selectByPage(page, pageSize);
        PageInfo pageInfo = new PageInfo<>(empList);
        pageInfo.setTotal(empMapper.selectCount());
        return R.ok(pageInfo);
    }
}
@Mapper
public class EmpMapper{
    List selectByPage(@Param("page") Integer pageNum,
                           @Param("size") Integer pageSize);
    Integer selectCount();
}

SQL




测试返回数据

        返回的分页信息是在data中的list

【SpringBoot】MyBatis与MyBatis-Plus分页查询 & github中的PageHelper,第1张

MyBatis-Plus

引入依赖





    com.alibaba
    druid
    1.1.22



    com.baomidou
    mybatis-plus-boot-starter
    3.4.3



    mysql
    mysql-connector-java
    8.0.17

【SpringBoot】MyBatis与MyBatis-Plus分页查询 & github中的PageHelper,第2张

修改yml

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/table?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml  # 对应xml的位置
  type-aliases-package: com.chf.entity  # 对应namespace的实体类包名
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 日志类型
    map-underscore-to-camel-case: true  # 字段与属性的驼峰规则

业务代码

@RestController
@RequestMapping("/emp")
public class EmpController{
   
    @Autowired
    private EmpService empService; 
    /**
     * 分页查询
     * @param pageNum 当前页数
     * @param pageSize 每页条数
     * @return
     */
    @GetMapping("/findAll/{page}/{size}")
    public R findAll(@PathVariable("page") Integer pageNum, @PathVariable("size") Integer pageSize){
        Page page = new Page<>(pageNum, pageSize);
        // 此方法还可以传入第二个参数:QueryWrapper条件构造器
        // 用于增添一些查询条件用的 这里就不做展示了
        empService.page(page);
        // 如果是调用数据访问层的就是selectPage()方法即以下语句
        // mapper.selectPage(page, QueryWrapper);
        return R.ok(page);
    }
}
public interface EmpService extends IService {
}
@Service
public class EmpServiceImpl extends ServiceImpl implements EmpService {
}
@Mapper
public interface EmpMapper extends BaseMapper {
}

测试返回数据

        可以看到在MyBatis-Plus返回前端的参数中使用records封装分页信息。看到这里以为结束了吗?仔细看total(总条数)会发现怎么会是0?还有pages(总页数)也是0,学过MyBatis-Plus应该都知道为了完成分页所以还需要配置分页插件才可以实现真正的分页。所以需要再添加一个配置类,代码如下:

【SpringBoot】MyBatis与MyBatis-Plus分页查询 & github中的PageHelper,第3张

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

         添加好分页插件再次查询接口就会看到总条数和总页数都是真实的数值了。

【SpringBoot】MyBatis与MyBatis-Plus分页查询 & github中的PageHelper,第4张

PageHelper

        其实MyBatis底层就是调用Github的PageHelper插件工具。但是如果直接使用Github的分页的话会更加简便,但是有一个坑。只需要将上面MyBatis的业务代码改为以下代码块。相比而言,代码行数一致。多了一句PageMethod.startPage()、少了一句设置总条数的语句。

业务代码

@RestController
@RequestMapping("/emp")
public class EmpController{
   
    //@Autowired
    //private EmpService empService; 
    // 为了博客简化代码量 所以直接调用Dao层接口
    @Autowired
    private EmpMapper empMapper;
    /**
     * 分页查询
     * @param pageNum 当前页数
     * @param pageSize 每页条数
     * @return
     */
    @GetMapping("/findAll/{page}/{size}")
    public R findAll(@PathVariable("page") Integer pageNum, @PathVariable("size") Integer pageSize){
        // 由于PageHelper继承PageMethod但未重写方法 所以写成下面的语句
        Page page = PageMethod.startPage(pageNum, pageSize);
        // 获取查询出的列表
        List empList = empMapper.selectByPage(page, pageSize);
        PageInfo pageInfo = new PageInfo<>(empList);
        // pageInfo.setTotal(empMapper.selectCount());
        // 此时page已经有列表信息、与总条数了 无需再一条SQL查询总条数
        return R.ok(page);
    }
}
​ 

踩坑

        这里的踩坑处就是Page对象一定要在想要查询的列表前先初始出来,否则过滤无效。这样子Paga中的列表信息才是我们想要的查询出来的信息。比如说:业务中我需要查询A、B列表进行拼接啥的。主要是为了返回A列表的数据,所以只有以下两种组合方式(大于号表示业务代码中靠前出现):

        Page > AList > BList。BList > Page > AList。

        关于这个问题。我很好奇的追入源码中阅读。在PageMethod的startPage()中对Page对象进行设置。并且多了一步判断当前线程(ThreadLocal)是否存在Page对象。不存在则创建,存在则直接分页。至于源码追踪请参考这篇文章:Mybatis第三方PageHelper插件分页原理-腾讯云开发者社区-腾讯云

public abstract class PageMethod {
    protected static final ThreadLocal LOCAL_PAGE = new ThreadLocal();
    public static  Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        // 判断当前线程中是否存在Page对象(执行排序Order by之后) 
        Page oldPage = getLocalPage(); // (Page)LOCAL_PAGE.get()
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        setLocalPage(page); // LOCAL_PAGE.set(page);
        return page;
    }
}