一、 自行车

有时候我们可能会用到,自己业务代码查出来一个List,然后用sublist进行手动分页。

手动分页就了解清楚List的subList方法使用就了,但是这是很可取的,如果返回值太大,内存容易被无情撑爆。

import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.*;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
public class TestMain03 {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过sesson获取Mapper 这个Mapper会编程Mybatis的代理Mapper
TestMapper mapper = session.getMapper(TestMapper.class);
// 业务传递进来的分页参数
Integer pageNum = 1; // 当前页(一般与前端交互 就迁就一下他们 页数从1开始)
Integer pageSize = 10; // 每页大小
// 查询全部数据 有时候可能调用第三方或者其他接口 只有全部数据
// 又想分页展示 但是人家不给你提供分页接口 这时候可能会用到这种
List<TestEntity> list = mapper.select();
List<TestEntity> res = new ArrayList<>();
if (list != null) {
// Params:
//fromIndex – low endpoint (包含) of the subList
//toIndex – high endpoint (不包含) of the subList
// 如果符合: (fromIndex < 0 || toIndex > size || fromIndex > toIndex) 抛异常 IndexOutOfBoundsException –
int size = list.size();
// 防止 fromIndex < 0
if (pageNum <= 0) {
throw new Exception("pageNum必须是大于等于1的整数");
}
if (pageSize <= 0) {
throw new Exception("pageSize必须是大于等于1的整数");
}
int totalPage = (int) Math.ceil(Double.valueOf(size)/Double.valueOf(pageSize));
// 页数超了最大页数 (前端不可信)
if (pageNum > pageSize) {
res = new ArrayList<>();
} else {
Integer fromIndex = (pageNum -1 ) * pageSize;
Integer toIndex =pageNum * pageSize;
// 防止 toIndex > size
toIndex = toIndex > list.size() ? list.size() : toIndex;
res = list.subList(fromIndex, toIndex);
}
}
for (int i = 0; i < res.size(); i++) {
System.out.println(res.get(i));
}
}
}
}

二、摩托车

Mybatis自带了RowBounds

看过源码就知道其实他是逻辑分页,也会把所有数据都查出来,然后内部进行了一个操作,先过滤掉offset大小的条数,然后继续读取limit大小的条数,最后返回。

// 如果有多个参数 在最后加上这个参数就可以 
@Select("select * from test")
List<TestEntity> selectRowBounds(RowBounds rd);

import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
public class TestMain04 {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过sesson获取Mapper 这个Mapper会编程Mybatis的代理Mapper
TestMapper mapper = session.getMapper(TestMapper.class);
// offset偏移量从0开始 limit一共查几条
Integer pageNum =1; // 当前页(一般与前端交互 就迁就一下他们 页数从1开始)
Integer pageSize = 10; // 每页大小
// 分页参数错误判断就先不判断了 根据业务自己判断
//
RowBounds rd = new RowBounds((pageNum-1)*pageSize,pageSize);
List<TestEntity> res = mapper.selectRowBounds(rd);
for (int i = 0; i < res.size(); i++) {
System.out.println(res.get(i));
}
}
}
}

看他的源码实现是简单了解是这样的:

// 跨过offset条记录 
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
// 也就是调动rs.next(); 写过原生sql的都知道这是
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
}
}

//
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
// 跳过offset条记录
skipRows(rsw.getResultSet(), rowBounds);
// 从剩余结果中读取limit条记录
// 每读取一条记录 就给resultContext++
while (shouldProcessMoreRows(resultContext, rowBounds)
&& rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
// 这里就是比较读取的数 是否小于limit 如果小于就接着循环读取
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}

说白了也是通过代码逻辑处理

三、手动挡小轿车

物理分页,也就是不会查到客户端,在服务端已经分好了,

@Select("select * from test limit #{offset} , #{pageSize}")
List<TestEntity> selectLimit(@Param("offset") int offset, @Param("pageSize") Integer pageSize);

import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
public class TestMain05 {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过sesson获取Mapper 这个Mapper会编程Mybatis的代理Mapper
TestMapper mapper = session.getMapper(TestMapper.class);
// offset偏移量从0开始 limit一共查几条
Integer pageNum =1; // 当前页(一般与前端交互 就迁就一下他们 页数从1开始)
Integer pageSize = 10; // 每页大小
// 分页参数错误判断就先不判断了 根据业务自己判断
List<TestEntity> res = mapper.selectLimit((pageNum-1)*pageSize, pageSize);
for (int i = 0; i < res.size(); i++) {
System.out.println(res.get(i));
}
}
}
}

为什么说他是小轿车呢,因为这个对系统内存消耗最小,而且对网络消耗也是最小的。他会在服务端就把不需要的行过滤掉。

四、自动挡小轿车

有人说了,自己拼limit 多没劲,能不能稍微智能点儿,当然可以了,只要你想要的,我就能给。

自定义参数基类PageQo

package entity;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-21 17:37
*/
public class PageQo {
// 当前页数 1开始
Integer pageNum;
// 每页大小
Integer pageSize;

public PageQo() {
}
public PageQo(Integer pageNum, Integer pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
}

public Integer getPageNum() {
return pageNum;
}

public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}

public Integer getPageSize() {
return pageSize;
}

public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
}

自定义拦截器MyPageInterceptor

package interceptor;

import entity.PageInfo;
import entity.PageQo;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Properties;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyPageInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("执行拦截器代码");
// 获取参数 这里指定如果参数是PageQo的子类,才会进行分页 其他不进行分页
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//获取StatementHandler的包装类
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
BoundSql boundSql = statementHandler.getBoundSql();
Object param = boundSql.getParameterObject();
// 参数是PageQo的子类 才会进行分页操作
if(param instanceof PageQo) {
// 强转 主要是为了分页参数
PageQo pageQo = (PageQo) param;
//获取原始SQL语句
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
System.out.println("原sql:"+originalSql);
String sql = originalSql.trim() + " limit " + (pageQo.getPageNum()-1)* pageQo.getPageSize() + ", " + pageQo.getPageSize();
System.out.println("分页sql:"+sql);
metaObject.setValue("delegate.boundSql.sql", sql);
}
// 默认不进行分页逻辑
return invocation.proceed();
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}

@Override
public void setProperties(Properties properties) {

}
}

配置文件配置拦截器mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<!--扫描包路径-->
<typeAliases>
<package name="entity"/>
</typeAliases>
<!--自定义拦截器-->
<plugins>
<plugin interceptor="interceptor.MyPageInterceptor"></plugin>
</plugins>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--扫描-->
<mappers>
<mapper class="dao.TestMapper"/>
</mappers>

</configuration>

测试:

import dao.TestMapper;
import entity.PageQo;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
public class TestMain06 {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过sesson获取Mapper 这个Mapper会编程Mybatis的代理Mapper
TestMapper mapper = session.getMapper(TestMapper.class);
// offset偏移量从0开始 limit一共查几条
Integer pageNum =1; // 当前页(一般与前端交互 就迁就一下他们 页数从1开始)
Integer pageSize = 10; // 每页大小
// 分页参数错误判断就先不判断了 根据业务自己判断
List<TestEntity> res = mapper.selectPage(new PageQo(pageNum, pageSize));
for (int i = 0; i < res.size(); i++) {
System.out.println(res.get(i));
}
}
}
}

输出:

浅谈 Mybatis 分页_sql

Mybatis拦截器与我们平时web项目拦截器一个道理,就是一种机制,可以进行特殊扩展的,学过SpringBoot源码的人知道其实他也有类似的机制叫PostProcessor 可以自定义自己的业务实现

可以看到之后是修改了原始SQL 所以这个也是服务器端进行的分页,物理分页。

五、自动驾驶小轿车

PageHelper

他是一些来自互联网的高手自定义了Interceptor ,可以直接引入使用

等我们有能力了,一定要多给人提供开源的东西(写博客也算一种贡献方式)

大家使用的时候一定注意:

PageHelper.startPage() ;后面紧接着一定是Mapper的调用方法,

引入pom.xml

<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<!--520 真好 代码对我说:我爱你-->
<version>5.2.0</version>
</dependency>

使用方式一:

与自定义Interceptor一样,mybatis-config.xml引入

<!--自定义拦截器-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>

import com.github.pagehelper.PageHelper;
import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
public class TestMain07 {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过sesson获取Mapper 这个Mapper会编程Mybatis的代理Mapper
TestMapper mapper = session.getMapper(TestMapper.class);
// offset偏移量从0开始 limit一共查几条
Integer pageNum =1; // 当前页(一般与前端交互 就迁就一下他们 页数从1开始)
Integer pageSize = 10; // 每页大小
// 1. 最常用使用方式
// https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md
PageHelper.startPage(pageNum, pageSize);
List<TestEntity> res = mapper.select();
for (int i = 0; i < res.size(); i++) {
System.out.println(res.get(i));
}
}
}
}

使用方式二:

自定义pageNum和pageSize 参数名称

mybatis-config.xml 需要修改

<!--自定义拦截器-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="supportMethodsArguments" value="true"/>
<!--自定义key值 闲的没事儿搞一个特殊的值 也是没谁了 一般我们就用pageSize pageNum-->
<property name="params" value="pageNum=myPageNum;pageSize=myPageSize;"/>
</plugin>
</plugins>

Mapper

@Select("select * from test")
List<TestEntity> select01(@Param("myPageNum") Integer pageNum, @Param("myPageSize") Integer pageSize);

@Select("select * from test")
List<TestEntity> select02(Map<String, Integer> params);

Test

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*/
public class TestMain08 {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// 通过sesson获取Mapper 这个Mapper会编程Mybatis的代理Mapper
TestMapper mapper = session.getMapper(TestMapper.class);
// offset偏移量从0开始 limit一共查几条
Integer pageNum =1; // 当前页(一般与前端交互 就迁就一下他们 页数从1开始)
Integer pageSize = 10; // 每页大小
List<TestEntity> all = mapper.select();
System.out.println("总条数:"+all.size());
// 1. 参数方式
// https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/en/HowToUse.md
List<TestEntity> res = mapper.select01(pageNum, pageSize);
System.out.println("分页条数01:"+res.size());
// 2. 支持 ServletRequest,Map,POJO 对象,
// request难获取,这里举一个Map 的例子吧
Map<String, Integer> params = new HashMap<>();
params.put("myPageNum", pageNum);
params.put("myPageSize", pageSize);
List<TestEntity> res02 = mapper.select02(params);
System.out.println("分页条数02:"+res02.size());
// 可以看到这里返回类型用List接收的,但是此list非彼list
// 其实他返回的是一个com.github.pagehelper.Page 他继承了ArrayList
// 这个page除了我们看到的数据以为 还有很多高级的东西
// 我们可以直接转换成他提供的PageInfo
PageInfo pageInfo = new PageInfo(res02);
System.out.println("当前页数pageNum:"+pageInfo.getPageNum());
System.out.println("分页大小pageSize:"+pageInfo.getPageSize());
System.out.println("上一页页数prePage:"+pageInfo.getPrePage());
System.out.println("一共多少页:"+pageInfo.getPages());
System.out.println("当前页条数:"+pageInfo.getSize());
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("记录开始行数:"+pageInfo.getStartRow());
System.out.println("记录结束行数:"+pageInfo.getEndRow());

System.out.println("------------华丽丽的分割线-----------------");
// 或者强转成Page使用也是ok的
Page page = (Page)res02;
System.out.println("当前页数pageNum:"+page.getPageNum());
System.out.println("分页大小pageSize:"+page.getPageSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("当前页条数:"+page.size());
System.out.println("总条数:"+page.getTotal());
System.out.println("记录开始行数:"+page.getStartRow());
System.out.println("记录结束行数:"+page.getEndRow());
}
}
}

浅谈 Mybatis 分页_apache_02

其他用法:

它还可以指定是否使用count语句统计总条数,我之前同事遇到过,由于count一次耗费性能很大,所以只在第一次查询的时候count一次,如果再一次查询前端把count带过来,这边就不再进行count查询

六、唠唠

有人觉得PageHelper很神奇,为什么startPage调用,下边的方法就知道该分页,而且还能获取分页参数。

这里其实用到了一个知识点叫:ThreadLocal

startPage其实是把参数放到了ThreadLocal当中,当走到Interceptor的时候,会去ThreadLocal中找对应参数。

这个知识点以后会写相关文章来简单讲一下ThreadLocal的使用,这个知识点在项目需要动态切换数据源的时候也会用到。

有事儿没事儿关注公众号:木子的昼夜编程