mybatis常见分页技术和自定义分页原理实战

简介: mybatis常见分页技术和自定义分页原理实战


前言

这是一篇简单总结,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(0) from 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(0) from 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拼接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统一对象为我们做了什么。

    使用上就是使用pageHelper.startPage(…)这个方法。
    我们看下他的实现原理:
  • 1.pageHelper.startPage(…)

继续跟

这里面会获取到一个Page对象,放在ThreadLocal对象中,后面会根据是否有page对象进行是否进行分页:

  • 拦截器
    很熟悉,就是我们之前自定义分页拦截器一样的原理。

    也是实现了mybatis的拦截器进行处理sql

    获取总数的方法
  • 在拦截器里面看下怎么过滤需不需要分页的?



    没有分页的话,我们不会调用 PageHelper.startPage方法,也就从ThreadLocal LOCAL_PAGE这个属性中取不出page,就不分页。
    如果不分页就走自己的不分页的sql:
  • 返回时包装成PageInfo统一对象为我们做了什么。

    返回后page只给我们赋值了记录数。所以需要我们手动把处理后的数据,放在返回pageInfo对象中:

显示调用

new pageInfo的逻辑其实就是:PageInfo因为继承了PageSerializable,所以含有total和list2个属性,给与其赋值。

这样pagehelper的分页就简单梳理完了。

再来聊聊另一个分页插件。

mybats-plus的分页插件
使用

mybats-plus的分页插件的使用先很简单

首先引入其分页插件:

使用mybatis-plus分页插件

在mapper中传入Ipage对象

然后在sql中写我们的分页sql就可以了。

分析

个人觉得mybatis-plus分页很清晰,不是很绕。

这里显示在StatementHandler拼接sql的时候可以做些事情:

对StatementHandler的prepare方法处进行拦截添加业务逻辑

拦截方法: 判断是否需要分页

分页操作:

查询记录数

拼装分页语句获取集合

拼接分页数据

获取总记录数赋值给page对象

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


以上就是mybatis的分页个人理解,并实现了简单的粗糙的自定义分页插件封装,对第三方的分页插件进行浅浅的分析。


相关文章
|
26天前
|
SQL XML Java
mybatis Mapper的概念与实战
MyBatis 是一个流行的 Java 持久层框架,它提供了对象关系映射(ORM)的功能,使得Java对象和数据库中的表之间的映射变得简单。在MyBatis中,Mapper是一个核心的概念,它定义了映射到数据库操作的接口。简而言之,Mapper 是一个接口,MyBatis 通过这个接口与XML映射文件或者注解绑定,以实现对数据库的操作。
31 1
|
1月前
|
SQL Java 数据库连接
|
2月前
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
36 1
|
20天前
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
18 1
|
3月前
|
SQL Oracle 关系型数据库
整合Mybatis-Plus高级,Oracle 主键Sequence,Sql 注入器实现自定义全局操作
整合Mybatis-Plus高级,Oracle 主键Sequence,Sql 注入器实现自定义全局操作
83 0
|
3月前
|
SQL 数据库
在mybatis-plus怎么使用自定义的sql语句
在mybatis-plus怎么使用自定义的sql语句
60 0
|
17天前
|
SQL Java 数据库连接
Mybatis是如何实现分页功能的
Mybatis是如何实现分页功能的
10 0
|
1月前
|
XML Java 数据库连接
【MyBatis】 框架原理
【MyBatis】 框架原理
15 0
|
1月前
|
存储 缓存 Java
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制