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 如下图。

66ba272a0bfc97be54a5fa679e3d5482.png

分页无非就是获取总数和获取每页的数据,我们自定义实现拦截器,在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 * from test


mapper

 

//插件分页
  List> getProductByPage(Map 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 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分页插件

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


让我们封装进什么对象作为参数传入接口

分页拦截器里面哪里执行了分页控制,即是否要进行分页

分页拦截器里面在哪里给我们写了查询总数count的接口,在哪里执行了分页查询

返回时包装成PageInfo统一对象为我们做了什么。

46a9d80a6e05e4e3b19d57a0ee70bcdf.png

使用上就是使用pageHelper.startPage(…)这个方法。

我们看下他的实现原理:

1.pageHelper.startPage(…)

66ba272a0bfc97be54a5fa679e3d5482.png

继续跟

1dc618a0ed9580ce8bfa6facb208c08f.png


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

5d4c6812c8535adbb050f4ddf2e1bce8.png


拦截器

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

46a9d80a6e05e4e3b19d57a0ee70bcdf.png

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

66ba272a0bfc97be54a5fa679e3d5482.png

获取总数的方法

1dc618a0ed9580ce8bfa6facb208c08f.png

在拦截器里面看下怎么过滤需不需要分页的?

5d4c6812c8535adbb050f4ddf2e1bce8.png46a9d80a6e05e4e3b19d57a0ee70bcdf.png66ba272a0bfc97be54a5fa679e3d5482.png



没有分页的话,我们不会调用 PageHelper.startPage方法,也就从ThreadLocal LOCAL_PAGE这个属性中取不出page,就不分页。


相关文章
|
1月前
|
SQL XML Java
mybatis Mapper的概念与实战
MyBatis 是一个流行的 Java 持久层框架,它提供了对象关系映射(ORM)的功能,使得Java对象和数据库中的表之间的映射变得简单。在MyBatis中,Mapper是一个核心的概念,它定义了映射到数据库操作的接口。简而言之,Mapper 是一个接口,MyBatis 通过这个接口与XML映射文件或者注解绑定,以实现对数据库的操作。
38 1
|
1月前
|
SQL Java 数据库连接
|
2月前
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
40 1
|
1月前
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
Mybatis+mysql动态分页查询数据案例——分页工具类(Page.java)
22 1
|
17天前
|
存储 关系型数据库 MySQL
【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)
【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)
|
17天前
|
Java 关系型数据库 MySQL
【mybatis-plus】自定义多数据源,动态切换数据源事务失效问题
【mybatis-plus】自定义多数据源,动态切换数据源事务失效问题
【mybatis-plus】自定义多数据源,动态切换数据源事务失效问题
|
29天前
|
SQL Java 数据库连接
Mybatis是如何实现分页功能的
Mybatis是如何实现分页功能的
10 0
|
1月前
|
XML Java 数据库连接
【MyBatis】 框架原理
【MyBatis】 框架原理
17 0
|
1月前
|
存储 缓存 Java
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制
什么!?实战项目竟然撞到阿里面试的原题!???关于MyBatis Plus的缓存机制