mybatis常用分页写法和自定义分页原理实战


  • 前言

    • mybatis简单了解

  • 分页类型

  • 分页方式

    • 1.数组分页

    • 2.数据库分页

    • 3.Rowbounds分页

    • 4.自定义插件分页

    • 5.聊下第三方分页插件


前言

这是一篇简单总结,mybatis分页的文章。

mybatis简单了解

在很久以前,我们会使用jdbc对数据库进行crud,随着ORM框架的诞生,为了效率,可能就选择了hibernate和mybatis等技术。hibernate由于比较笨重,虽然切换数据库也不会太大改变我们的程序,但是也不是太灵活,比如我们无法利用数据库比较重要的索引等技巧,所以现在很多项目中都是使用mybais的。今天只聊mybatis的分页技术。

分页类型

物理分页:直接从数据库中拿出我们需要的数据,例如在Mysql中使用limit。

逻辑分页:从数据库中拿出所有符合要求的数据,然后再从这些数据中拿到我们需要的分页数据。

分页方式

mybatis中几种写法:

1.数组分页

这种的思路是一次性查询所有数据,得到一个集合,然后在根据页码等参数进行切分数组,返回前端指定页码的数据。demo如下:

mapper层

//数组分页
List<Map<String, Object>> getProductByArrayPage();

sql语句

  select * from test

service层方法

    @Override
    public R getProductByArrayPage(int page, int limit) {
        List<Map<String,Object>> data = productMapper.getProductByArrayPage();
        //从第几条开始
        int startNum = (page-1)*limit;
        //到第几条结束
        int lastNum = page * limit;
        if (lastNum>data.size()){
            lastNum = data.size();
        }
        return R.success().set("count",data.size()).data(data.subList(startNum,lastNum));
    }
//统一返回格式(根据自己业务来定)
public class R extends HashMap{
    public static  String SUCCESS_CODE="200";
    public static String ERROR_CODE="500";
    public static String DATA_KEY = "data";
    public static String MSG_KEY = "msg";

    private R(){

    }

    public R set(String key, Object object){
        super.put(key,object);
        return  this;
    }

    private  static R ok(){
        return new R();
    }

    public static R success(){

        return R.ok().set("code", R.SUCCESS_CODE).set(R.MSG_KEY,"操作成功");
    }

    public static R success(String msg){

        return R.ok().set("code", R.SUCCESS_CODE).set(R.MSG_KEY,msg);
    }

    public static R success(String msg, Object object){

        return R.ok().set("code", R.SUCCESS_CODE).set(R.MSG_KEY,msg).set(R.DATA_KEY,object);
    }

    public R data(Object obj){
        return this.set("data",obj);
    }

    public static R error(){
        return R.ok().set(R.MSG_KEY,"操作失败").set("code", R.ERROR_CODE);
    }

    public static R error(String msg){
        return R.ok().set(R.MSG_KEY,msg).set("code", R.ERROR_CODE);
    }

    public static R error(String msg, Object object){
        return R.ok().set(R.MSG_KEY,msg).set(R.DATA_KEY,object).set("code", R.ERROR_CODE);
    }

}

2.数据库分页

思路:手动写一个查询集合的sql,一个获取总数的接口,属于物理分页。

  • sql层
 <select id="getProductPage" parameterType="Map" resultType="Map">
       select * from test limit #{start} , #{limit}
 </select>

<select id="getProductCount"  resultType="int">
       select count(0from test 
 </select>

  • mapper层
    //数据库分页
List<Map<String, Object>> getProductPage(Map<String, Object> map);

    //获取总数
 int getProductCount();

  • service 层
public R getProductPage(int page,int limit) {
    Map<String,Object> map = new HashMap<>();
    map.put("start",(page-1)*limit);
    map.put("limit",limit);
    System.out.println("调用了sql分页");
    return R.success().data(productMapper.getProductPage(map)).set("count",productMapper.getProductCount());
}

3.Rowbounds分页

Mybatis可以通过传递RowBounds对象,来进行数据库数据的分页操作,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页。demo如下: sql:

<select id="getProductByPage" parameterType="Map" resultType="Map">
     select * from test
 </select>
<select id="getProductCount"  resultType="int">
      select count(0from test
</select>

mapper层

//Rowbounds分页
 List<Map<String, Object>> getProductByRowboundsPage(RowBounds rowBounds);
//获取总数
int getProductCount();
  • service 层
public R getProductByRowboundsPage(int page,int limit) {
    RowBounds rowBounds = new RowBounds((page-1)*limit,limit);
    System.out.println("调用了RowBounds分页");
    return R.success().data(productMapper.getProductByRowboundsPage(rowBounds)).set("count",productMapper.getProductCount());
}

4.自定义插件分页

自定义分页原理

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

Interceptor 如下图。mybatis常用分页写法和自定义分页原理实战分页无非就是获取总数和获取每页的数据,我们自定义实现拦截器,在mybatis拼接sql时,自动为我们计算count记录数,然后执行分页查询即可。

自定义分页实战

demo如下:

  • 自定义分页插件(拦截器)
//args : 你需要mybatis传入什么参数给你 type :你需要拦截的对象  method=要拦截的方法
@Intercepts(@Signature(type = StatementHandler.class,method ="prepare",args = {Connection.class,Integer.class}))
public class MyPagePlugin implements Interceptor 
{

    String databaseType = "";

    String pageSqlId = "";

    public String getDatabaseType() {
        return databaseType;
    }

    public void setDatabaseType(String databaseType) {
        this.databaseType = databaseType;
    }

    public String getPageSqlId() {
        return pageSqlId;
    }

    public void setPageSqlId(String pageSqlId) {
        this.pageSqlId = pageSqlId;
    }

    //我们自己拦截器里面的逻辑
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

//        statementHandler.getDelegate().getmappedStatement().getId();
//        Field delegate = StatementHandler.class.getDeclaredField("delegate");
//        delegate.setAccessible(true);
//        Object o = delegate.get(statementHandler);
//        o.getClass().getDeclaredField("mappedStatement").

        MetaObject metaObject = MetaObject.forObject(
                statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
        String sqlId = (String)metaObject.getValue("delegate.mappedStatement.id");
        //判断一下是否是分页
//        <!--第一步 执行一条couunt语句-->
        //1.1拿到连接
        //1.2 预编译SQL语句 拿到绑定的sql语句
        //1.3 执行 count语句   怎么返回你执行count的结果

//    <!--第二部 重写sql  select * from luban_product  limit start,limit -->
        //2.1 ? 怎么知道 start 和limit
        //2.2拼接start 和limit
        //2.3 替换原来绑定sql
        //拿到原来应该执行的sql
        if (sqlId.matches(pageSqlId)){
            ParameterHandler parameterHandler = statementHandler.getParameterHandler();
            //原来应该执行的sql
            String sql = statementHandler.getBoundSql().getSql();
            //sql= select * from  product    select count(0) from (select * from  product) as a
            //select * from luban_product where name = #{name}
            //执行一条count语句
            //拿到数据库连接对象
            Connection connection = (Connection) invocation.getArgs()[0];
            String countSql = "select count(0) from ("+sql+") a";
            System.out.println(countSql);
            //渲染参数
            PreparedStatement preparedStatement = connection.prepareStatement(countSql);
            //条件交给mybatis
            parameterHandler.setParameters(preparedStatement);
            ResultSet resultSet = preparedStatement.executeQuery();
            int count =0;
            if (resultSet.next()) {
                count = resultSet.getInt(1);
            }
            resultSet.close();
            preparedStatement.close();
            //获得你传进来的参数对象
            Map<String, Object> parameterObject = (Map<String, Object>) parameterHandler.getParameterObject();
            //limit  page
            PageUtil pageUtil = (PageUtil) parameterObject.get("page");
            //limit 1 ,10  十条数据   总共可能有100   count 要的是 后面的100
            pageUtil.setCount(count);

            //拼接分页语句(limit) 并且修改mysql本该执行的语句
            String pageSql = getPageSql(sql, pageUtil);
            metaObject.setValue("delegate.boundSql.sql",pageSql);
            System.out.println(pageSql);
        }
        //推进拦截器调用链
        return invocation.proceed();
    }

    public String getPageSql(String sql,PageUtil pageUtil){
        if(databaseType.equals("mysql")){
            return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
        }else if(databaseType.equals("oracle")){
            //拼接oracle的分语句
        }

        return sql+" limit "+pageUtil.getStart()+","+pageUtil.getLimit();
    }


    //需要你返回一个动态代理后的对象  target :StatementHandler
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    //会传入配置文件内容 用户可根据配置文件自定义
    @Override
    public void setProperties(Properties properties) {

    }
}

其他代码 sql

  <select id="getProductByPage" parameterType="Map" resultType="Map">
        select * from test
    </select>

mapper

    //插件分页
  List<Map<String, Object>> getProductByPage(Map<String, Object> map);

service层就很简单了。自定义个分页对象

@Data
public class PageUtil {

    private int page;

    private  int limit;

    private  int count;

    private  int start;

//分页的话 入参对象中添加page对象属性。

@Override
public R getProductByPage(int page,int limit) {
    Map<String,Object> map =new HashMap<>();
    //只要在参数中 添加page属性就自动我们分页了
    PageUtil pageUtil = new PageUtil(page,limit);
    map.put("page",pageUtil);
    return R.success().data(productMapper.getProductByPage(map)).set("count",pageUtil.getCount());
}

聊下第三方分页插件

其他的第三方插件页无非就是和我们自定义插件一样的原理,只是做了其他优化,比如放在一个ThreadLocal中保证线程安全,对sql进行优化等。

pageHelper分页插件

这个分页插件功能代码很多,我们抓主干只看关键的地方:

  1. 让我们封装进什么对象作为参数传入接口
  2. 分页拦截器里面哪里执行了分页控制,即是否要进行分页
  3. 分页拦截器里面在哪里给我们写了查询总数count的接口,在哪里执行了分页查询
  4. 返回时包装成PageInfo统一对象为我们做了什么。mybatis常用分页写法和自定义分页原理实战使用上就是使用pageHelper.startPage(..)这个方法。我们看下他的实现原理:
  • 1.pageHelper.startPage(..)mybatis常用分页写法和自定义分页原理实战

继续跟mybatis常用分页写法和自定义分页原理实战

这里面会获取到一个Page对象,放在ThreadLocal对象中,后面会根据是否有page对象进行是否进行分页:mybatis常用分页写法和自定义分页原理实战

  • 拦截器 很熟悉,就是我们之前自定义分页拦截器一样的原理。mybatis常用分页写法和自定义分页原理实战也是实现了mybatis的拦截器进行处理sqlmybatis常用分页写法和自定义分页原理实战获取总数的方法mybatis常用分页写法和自定义分页原理实战
  • 在拦截器里面看下怎么过滤需不需要分页的?mybatis常用分页写法和自定义分页原理实战mybatis常用分页写法和自定义分页原理实战mybatis常用分页写法和自定义分页原理实战没有分页的话,我们不会调用 PageHelper.startPage方法,也就从ThreadLocalLOCAL_PAGE这个属性中取不出page,就不分页。如果不分页就走自己的不分页的sql:mybatis常用分页写法和自定义分页原理实战
  • 返回时包装成PageInfo统一对象为我们做了什么。mybatis常用分页写法和自定义分页原理实战返回后page只给我们赋值了记录数。所以需要我们手动把处理后的数据,放在返回pageInfo对象中:mybatis常用分页写法和自定义分页原理实战

显示调用mybatis常用分页写法和自定义分页原理实战new pageInfo的逻辑其实就是:PageInfo因为继承了PageSerializable,所以含有total和list2个属性,给与其赋值。mybatis常用分页写法和自定义分页原理实战

这样pagehelper的分页就简单梳理完了。再来聊聊另一个分页插件。

mybats-plus的分页插件

使用

mybats-plus的分页插件的使用先很简单 首先引入其分页插件:mybatis常用分页写法和自定义分页原理实战使用mybatis-plus分页插件mybatis常用分页写法和自定义分页原理实战在mapper中传入Ipage对象mybatis常用分页写法和自定义分页原理实战

然后在sql中写我们的分页sql就可以了。mybatis常用分页写法和自定义分页原理实战

分析

个人觉得mybatis-plus分页很清晰,不是很绕。这里显示在StatementHandler拼接sql的时候可以做些事情:mybatis常用分页写法和自定义分页原理实战对StatementHandler的prepare方法处进行拦截添加业务逻辑mybatis常用分页写法和自定义分页原理实战

拦截方法: 判断是否需要分页mybatis常用分页写法和自定义分页原理实战

分页操作:mybatis常用分页写法和自定义分页原理实战查询记录数mybatis常用分页写法和自定义分页原理实战拼装分页语句获取集合mybatis常用分页写法和自定义分页原理实战拼接分页数据mybatis常用分页写法和自定义分页原理实战

获取总记录数赋值给page对象mybatis常用分页写法和自定义分页原理实战

最后还是需要我们自己把集合对象赋值给Ipage对象

mybatis常用分页写法和自定义分页原理实战
图片

以上就是mybatis的分页个人理解,实现自定义分页插件封装,对第三方的分页插件进行总体介绍。


关注微信公众号: 怒放de每一天

不定时推送相关文章,期待和大家一起成长!!

图片


原文始发于微信公众号(怒放de每一天):mybatis常用分页写法和自定义分页原理实战

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/106787.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!