ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

浅谈 Mybatis 分页

2021-09-21 20:01:53  阅读:142  来源: 互联网

标签:浅谈 pageSize ibatis apache session org Mybatis import 分页


一、 自行车

有时候我们可能会用到,自己业务代码查出来一个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&amp;characterEncoding=utf-8&amp;useSSL=false&amp;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拦截器与我们平时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());
        }
    }
}

在这里插入图片描述

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

六、唠唠

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

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

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

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

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

标签:浅谈,pageSize,ibatis,apache,session,org,Mybatis,import,分页
来源: https://blog.csdn.net/qq_36291682/article/details/120404732

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有