MyBatis:实现简单物理分页(Plugin的使用)

简介: MyBatis中的SqlSession接口中提供的分页功能的方法// 获取sqlSession的步骤略,statement略,mapper中的映射语句为// select * from users List list = sqlSession.

MyBatis中的SqlSession接口中提供的分页功能的方法

// 获取sqlSession的步骤略,statement略,mapper中的映射语句为
// select * from users 
List<User> list = sqlSession.selectList(statement, null, new RowBounds(0,3));

MyBatis内置的分页处理器,是通过内存进行分页,结合上面的例子就是MyBatis首先执行select * from users,然后获取结果集ResultSet,接着通过传入的RowBounds中的offset和limit属性来对ResultSet进行加工。如果记录量大的话,这种效率无疑是相当低的。想证实上面这个结论,可以查看MyBatis中的DefaultResultSetHandler类。

我们可以使用mybatis 提供的plugin,实现sql执行前的拦截;在执行sql查询列表前,装配指定的分页select * from users limit #{offset},#{limit}

MyBATIS plugin初始化&原理

MyBATIS是在初始化上下文环境的时候就初始化插件的,mybatis 的plugin实质上就是拦截器。

MyBatis Plugin的实现采用了Java的动态代理,应用了责任链设计模式。可以在mybatis-config.xml中加入多个plugin,也就是可以加入多个<plugin>节点,多个拦截器采用链式执行。

<plugins>
        <plugin interceptor="com.ljheee.page.interceptor.PageInterceptor">
            <!--property指定分页参数-->
            <property name="page.limit" value="2"/>
        </plugin>
    </plugins>

在MyBatis中,只能拦截四种接口的实现类:

  • Executor
  • ParameterHandler
  • ResultSetHandler
  • StatementHandler

每种类型的拦截方式都是一样的。因此我们需要确定拦截什么对象、什么方法。

这个需要了解sqlSession的执行原理,可以参考文章:
MyBatis原理第四篇——statementHandler对象(sqlSession内部核心实现,插件的基础)

从文中可以知道执行查询是使用StatementHandler的prepare预编译SQL,使用parameterize设置参数,使用query执行查询。

我们希望的是在预编译前去修改sql,做出加入limit语句限制sql的返回。(这里我用的是Mysql,如果用其他数据库需要自己编写你自己的sql),因此我们要拦截prepare方法。

我们需要做的,除了在mybatis-config.xml中加入<plugin>,最重要的是编码实现org.apache.ibatis.plugin.Interceptor接口,并指定拦截StatementHandler的prepare()方法。

@Intercepts(@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
))
public class PageInterceptor implements Interceptor {
......
}

type 告诉要拦截什么对象,它可以是四大对象的一个。
method 告诉你要拦截什么方法。
args 告诉方法的参数是什么。

实现拦截器

在实现前我们需要熟悉一个mybatis中常用的类的使用。它便是:MetaObject。
它的作用是可以帮助我们取到一些属性和设置属性(包括私有的)。它有三个方法:

  • MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)

这个方法我们基本不用了,因为MyBATIS中可以用SystemMetaObject.forObject(Object obj)代替它。

  • Object getValue(String name)

  • void setValue(String name, Object value)

第一个方法是绑定对象,第二个方法是根据路径获取值,第三个方法是获取值。
这些说还是有点抽象,我们举个例子,比如说现在我有个学生对象(student),它下面有个属性学生证(selfcard),学生证也有个属性发证日期(date)。
但是发证日期是一个私有的属性且没有提供公共方法访问。我们现在需要访问它,那么我们就可以使用MetaObject将其绑定:

MetaObject metaStudent = SystemMetaObject.forObject(student);
这样便可以读取它的属性:
Date date =(Date) metaStudent.getValue("selfcard.date");
或者设置它的属性:
metaStudent.setValue("selfcard.date", new Date());

MetaObject只是MyBatis一个工具类。实现sql的拦截和修改,其实就是用metaObject.getValue拿到原来的sql,经过修改后再metaObject.setValue设置到Statement中。

拦截器的实现
package com.ljheee.page.interceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;

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

import static org.apache.ibatis.reflection.SystemMetaObject.*;

@Intercepts(@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
))
public class PageInterceptor implements Interceptor {

    private int limit = 0;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = forObject(statementHandler);

        // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过循环可以分离出最原始的的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = forObject(object);
        }

        //BoundSql对象是处理sql语句的。
        String sql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");

        //判断sql是否select语句,如果不是select语句那么就出错了。
        //如果是修改它,是的它最多返回行,这里用的是mysql,其他数据库要改写成其他
        if (sql != null && sql.toLowerCase().trim().indexOf("select") == 0 && !sql.contains("$_$limit_$table_")) {
            //通过sql重写来实现,这里我们起了一个奇怪的别名,避免表名重复.
            sql = "select * from (" + sql + ") $_$limit_$table_ limit " + this.limit;
            metaStatementHandler.setValue("delegate.boundSql.sql", sql); //重写SQL
        }
        return invocation.proceed();//实际就是调用原来的prepared方法,只是再次之前我们修改了sql
    }


    /**
     *通过Plugin的wrap(...)方法来实现代理类的生成操作
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {

        if(target instanceof StatementHandler ){
            return Plugin.wrap(target, this);//使用Plugin的wrap方法生成代理对象
        }else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties props) {
        String limitStr = props.get("page.limit").toString();
        this.limit = Integer.parseInt(limitStr);//用传递进来的参数初始化
    }

}



工程 https://github.com/ljheee/mybatis-pages


PageHelper介绍

PageHelper分页插件,支持物理分页。
PageHelper支持多种数据库,如Oracle、MySQL、SqlServer、PostgreSQL等,当前最新版本是4.1.6。

目录
相关文章
|
6月前
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
SpringBoot+Mybatis-Plus+PageHelper分页+多条件查询
165 0
|
3月前
|
SQL XML Java
8、Mybatis-Plus 分页插件、自定义分页
这篇文章介绍了Mybatis-Plus的分页功能,包括如何配置分页插件、使用Mybatis-Plus提供的Page对象进行分页查询,以及如何在XML中自定义分页SQL。文章通过具体的代码示例和测试结果,展示了分页插件的使用和自定义分页的方法。
8、Mybatis-Plus 分页插件、自定义分页
|
6天前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
15天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
54 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
SQL Java 数据库连接
解决mybatis-plus 拦截器不生效--分页插件不生效
本文介绍了在使用 Mybatis-Plus 进行分页查询时遇到的问题及解决方法。依赖包包括 `mybatis-plus-boot-starter`、`mybatis-plus-extension` 等,并给出了正确的分页配置和代码示例。当分页功能失效时,需将 Mybatis-Plus 版本改为 3.5.5 并正确配置拦截器。
612 6
解决mybatis-plus 拦截器不生效--分页插件不生效
|
2月前
|
SQL XML Java
springboot整合mybatis-plus及mybatis-plus分页插件的使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis-Plus及其分页插件,包括依赖引入、配置文件编写、SQL表创建、Mapper层、Service层、Controller层的创建,以及分页插件的使用和数据展示HTML页面的编写。
springboot整合mybatis-plus及mybatis-plus分页插件的使用
|
3月前
|
Java 数据库 Spring
MyBatisPlus分页插件在SpringBoot中的使用
这篇文章介绍了如何在Spring Boot项目中配置和使用MyBatis-Plus的分页插件,包括创建配置类以注册分页拦截器,编写测试类来演示如何进行分页查询,并展示了测试结果和数据库表结构。
MyBatisPlus分页插件在SpringBoot中的使用
|
3月前
|
SQL Java 关系型数据库
MyBatis-Plus 分页魅力绽放!紧跟技术热点,带你领略数据分页的高效与便捷
【8月更文挑战第29天】在 Java 开发中,数据处理至关重要,尤其在大量数据查询与展示时,分页功能尤为重要。MyBatis-Plus 作为一款强大的持久层框架,提供了便捷高效的分页解决方案。通过封装数据库分页查询语句,开发者能轻松实现分页功能。在实际应用中,只需创建 `Page` 对象并设置页码和每页条数,再通过 `QueryWrapper` 构建查询条件,调用 `selectPage` 方法即可完成分页查询。MyBatis-Plus 不仅生成分页 SQL 语句,还自动处理参数合法性检查,并支持条件查询和排序等功能,极大地提升了系统性能和稳定性。
55 0
|
4月前
|
SQL 关系型数据库 Java
mybatis-分页
1. MyBatis RowBounds分页:先查询所有结果,再进行内存分页。 2. PageHelper插件:自动识别数据库类型并添加对应分页关键字,分两步执行:添加分页查询,然后查询总数。 3. SQL分页:直接在SQL中使用`LIMIT`或`ROWNUM`等进行分页。 4. 数组分页:DAO层查询所有数据,Service层通过`subList`方法实现分页。 5. 拦截器分页:自定义拦截器对特定方法进行拦截,并在SQL语句中添加分页参数。 6. 总结:逻辑分页适合小数据量,物理分页适合大数据量避免内存溢出。物理分页优于逻辑分页。