Heim >Java >JavaBase >Mehrere Möglichkeiten zum Paging in Mybatis

Mehrere Möglichkeiten zum Paging in Mybatis

青灯夜游
青灯夜游Original
2023-01-04 16:23:107172Durchsuche

Mybatis-Paging-Methode: 1. Verwenden Sie Arrays zum Paging. Fragen Sie zuerst alle Daten ab und fangen Sie dann die erforderlichen Teile aus der Liste ab. 2. Verwenden Sie die SQL-Anweisung, um Paging durchzuführen, und fügen Sie nach der SQL-Anweisung die Limit-Paging-Anweisung hinzu. 3. Verwenden Sie den Interceptor zum Paging und fügen Sie die Limit-Anweisung über den Interceptor am Ende der SQL-Anweisung hinzu, um Paging-Abfragen durchzuführen. 4. Um RowBounds zum Implementieren von Paging zu verwenden, müssen Sie alle qualifizierten Daten auf einmal abrufen und dann die Big Data im Speicher verarbeiten, um den Paging-Effekt zu erzielen.

Mehrere Möglichkeiten zum Paging in Mybatis

Die Betriebsumgebung dieses Tutorials: Windows7-System, Java8, Dell G3-Computer.

Die Paging-Methoden von Mybatis können in zwei Hauptkategorien unterteilt werden:

1. Logisches Paging

2. Physisches Paging

Die spezifische Methode des Mybatis-Pagings

  • Paging mit Hilfe von Arrays ( logisches Paging)

  • Verwenden Sie SQL-Anweisungen für Paging (physisches Paging)

  • Interceptor-Paging (physisches Paging) Verwenden Sie den Interceptor, um am Ende der SQL-Anweisung eine Limit-Anweisung hinzuzufügen, um ein für alle Mal das Beste abzufragen

  • RowBounds implementiert Paging (logisches Paging)

1. Paging mit Hilfe von Arrays

Prinzip: Rufen Sie beim Ausführen einer Datenbankabfrageoperation alle Datensätze ab, die die Bedingungen in der Datenbank erfüllen. Speichern Sie sie im temporären Array der Anwendung und rufen Sie dann mit der subList-Methode von List alle Datensätze ab, die die Bedingungen erfüllen.

Implementierung:

Erstellen Sie zunächst die StudentMapper-Schnittstelle auf der Dao-Ebene für Datenbankoperationen. Definieren Sie die Abfragemethode zum Durchblättern des Arrays in der Schnittstelle, wie unten gezeigt:

 List<Student> queryStudentsByArray();

Die Methode ist sehr einfach, das heißt, alle Daten abzurufen, sie über die Liste zu empfangen und dann den Paging-Vorgang auszuführen.

Erstellen Sie die Datei StudentMapper.xml und schreiben Sie die Abfrage-SQL-Anweisung:

 <select id="queryStudentsByArray"  resultMap="studentmapper">
        select * from student
 </select>

Es ​​ist ersichtlich, dass wir beim Schreiben der SQL-Anweisung keine Paging-bezogenen Vorgänge ausgeführt haben. Alle Studierendeninformationen finden Sie hier.

Als nächstes rufen Sie die Daten in der Serviceschicht ab und implementieren Paging:

Definieren Sie die IStuService-Schnittstelle und definieren Sie die Paging-Methode:

List<Student> queryStudentsByArray(int currPage, int pageSize);

Durch den Empfang des Parameters currPage wird angegeben, welche Datenseite angezeigt wird, und pageSize gibt die Nummer an der auf jeder Seite angezeigten Datenelemente.

Erstellen Sie die IStuService-Schnittstellenimplementierungsklasse StuServiceIml, um die Methode zu implementieren, und paginieren Sie das erhaltene Array über currPage und pageSize:

 @Override
    public List<Student> queryStudentsByArray(int currPage, int pageSize) {
        List<Student> students = studentMapper.queryStudentsByArray();
//        从第几条数据开始
        int firstIndex = (currPage - 1) * pageSize;
//        到第几条数据结束
        int lastIndex = currPage * pageSize;
        return students.subList(firstIndex, lastIndex);
    }

Rufen Sie alle Daten zwischen den beiden Indizes über die subList-Methode ab.

Erstellen Sie abschließend eine Testmethode im Controller:

  @ResponseBody
    @RequestMapping("/student/array/{currPage}/{pageSize}")
    public List<Student> getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
        List<Student> student = StuServiceIml.queryStudentsByArray(currPage, pageSize);
        return student;
    }

Erhalten Sie die angegebenen Daten über die vom Benutzer übergebenen currPage und pageSize.

Test:

Zuerst erhalten wir alle erhaltenen Daten, bevor der Paging-Effekt implementiert wird, wie unten gezeigt:

Mehrere Möglichkeiten zum Paging in Mybatis

Als nächstes geben Sie http://localhost:8080/student/ in den Browser student/ ein array/1/2Der Test implementiert Paging-Daten. Rufen Sie die Daten auf der ersten Seite ab und zeigen Sie auf jeder Seite zwei Datenelemente an. http://localhost:8080/student/student/array/1/2测试实现了分页后的数据。获取第一页的数据,每页显示两条数据。

结果如下:

Mehrere Möglichkeiten zum Paging in Mybatis 输出的是指定的从第0-2条数据,可见我们通过数组分页的功能是成功的。(这里因为用到了关联查询,所以看起来数据可能比较多)

缺点:数据库查询并返回所有的数据,而我们需要的只是极少数符合要求的数据。当数据量少时,还可以接受。当数据库数据量过大时,每次查询对数据库和程序的性能都会产生极大的影响。

2、借助Sql语句进行分页

在了解到通过数组分页的缺陷后,我们发现不能每次都对数据库中的所有数据都检索。然后在程序中对获取到的大量数据进行二次操作,这样对空间和性能都是极大的损耗。所以我们希望能直接在数据库语言中只检索符合条件的记录,不需要在通过程序对其作处理。这时,Sql语句分页技术横空出世。

实现:通过sql语句实现分页也是非常简单的,只是需要改变我们查询的语句就能实现了,即在sql语句后面添加limit分页语句。

首先还是在StudentMapper接口中添加sql语句查询的方法,如下:

List<Student> queryStudentsBySql(Map<String,Object> data);

然后在StudentMapper.xml文件中编写sql语句通过limiy关键字进行分页:

 <select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
        select * from student limit #{currIndex} , #{pageSize}
</select>

接下来还是在IStuService接口中定义方法,并且在StuServiceIml中对sql分页实现。

List<Student> queryStudentsBySql(int currPage, int pageSize);
 @Override
    public List<Student> queryStudentsBySql(int currPage, int pageSize) {
        Map<String, Object> data = new HashedMap();
        data.put("currIndex", (currPage-1)*pageSize);
        data.put("pageSize", pageSize);
        return studentMapper.queryStudentsBySql(data);
    }

sql分页语句如下:select * from table limit index, pageSize;

所以在service中计算出currIndex:要开始查询的第一条记录的索引。

测试:

在浏览器输入http://localhost:8080/student/student/sql/1/2

Die Ergebnisse sind wie folgt:

2 .png Die Ausgabe sind die angegebenen Daten von 0-2, was zeigt, dass unsere Paging-Funktion durch das Array erfolgreich ist. (Da hier zugehörige Abfragen verwendet werden, scheint es, dass viele Daten vorhanden sind.) 🎜🎜Nachteile: Die Datenbank fragt alle Daten ab und gibt sie zurück. Wir benötigen nur eine sehr kleine Datenmenge, die den Anforderungen entspricht. Wenn die Datenmenge gering ist, ist dies akzeptabel. Wenn die Menge der Datenbankdaten zu groß ist, hat jede Abfrage große Auswirkungen auf die Leistung der Datenbank und des Programms. 🎜🎜🎜🎜2. Verwenden Sie SQL-Anweisungen zum Paging. 🎜🎜🎜🎜Nachdem wir von den Mängeln beim Paging durch Arrays erfahren hatten, stellten wir fest, dass wir nicht jedes Mal alle Daten in der Datenbank abrufen können. Führen Sie dann sekundäre Operationen für die große Datenmenge durch, die im Programm erhalten wurde, was viel Platz und Leistung verbraucht. Daher hoffen wir, nur Datensätze direkt abzurufen, die die Bedingungen in der Datenbanksprache erfüllen, ohne sie durch das Programm zu verarbeiten. Zu dieser Zeit entstand die SQL-Anweisungs-Paging-Technologie. 🎜🎜Implementierung: Es ist auch sehr einfach, Paging über SQL-Anweisungen zu implementieren. Um dies zu erreichen, müssen wir lediglich die Anweisung unserer Abfrage ändern, dh nach der SQL-Anweisung eine Limit-Paging-Anweisung hinzufügen. 🎜🎜Fügen Sie zunächst die SQL-Anweisungsabfragemethode in der StudentMapper-Schnittstelle hinzu, wie folgt: 🎜
package com.cbg.interceptor;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

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

/**
 * Created by chenboge on 2017/5/7.
 * <p>
 * Email:baigegechen@gmail.com
 * <p>
 * description:
 */

/**
 * @Intercepts 说明是一个拦截器
 * @Signature 拦截器的签名
 * type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
 * method 拦截的方法
 * args 参数
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MyPageInterceptor implements Interceptor {

//每页显示的条目数
    private int pageSize;
//当前现实的页数
    private int currPage;

    private String dbType;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取StatementHandler,默认是RoutingStatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //获取statementHandler包装类
        MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);

        //分离代理对象链
        while (MetaObjectHandler.hasGetter("h")) {
            Object obj = MetaObjectHandler.getValue("h");
            MetaObjectHandler = SystemMetaObject.forObject(obj);
        }

        while (MetaObjectHandler.hasGetter("target")) {
            Object obj = MetaObjectHandler.getValue("target");
            MetaObjectHandler = SystemMetaObject.forObject(obj);
        }

        //获取连接对象
        //Connection connection = (Connection) invocation.getArgs()[0];


        //object.getValue("delegate");  获取StatementHandler的实现类

        //获取查询接口映射的相关信息
        MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");
        String mapId = mappedStatement.getId();

        //statementHandler.getBoundSql().getParameterObject();

        //拦截以.ByPage结尾的请求,分页功能的统一实现
        if (mapId.matches(".+ByPage$")) {
            //获取进行数据库操作时管理参数的handler
            ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
            //获取请求时的参数
            Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
            //也可以这样获取
            //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();

            //参数名称和在service中设置到map中的名称一致
            currPage = (int) paraObject.get("currPage");
            pageSize = (int) paraObject.get("pageSize");

            String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");
            //也可以通过statementHandler直接获取
            //sql = statementHandler.getBoundSql().getSql();

            //构建分页功能的sql语句
            String limitSql;
            sql = sql.trim();
            limitSql = sql + " limit " + (currPage - 1) * pageSize + "," + pageSize;

            //将构建完成的分页sql语句赋值个体&#39;delegate.boundSql.sql&#39;,偷天换日
            MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
        }
//调用原对象的方法,进入责任链的下一级
        return invocation.proceed();
    }


    //获取代理对象
    @Override
    public Object plugin(Object o) {
    //生成object对象的动态代理对象
        return Plugin.wrap(o, this);
    }

    //设置代理对象的参数
    @Override
    public void setProperties(Properties properties) {
//如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
        String limit1 = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limit1);
        this.dbType = properties.getProperty("dbType", "mysql");
    }
}
🎜Dann schreiben Sie die SQL-Anweisung in StudentMapper und implementieren Sie SQL-Paging in StuServiceIml. 🎜
private final StatementHandler delegate;

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        switch(RoutingStatementHandler.SyntheticClass_1.$SwitchMap$org$apache$ibatis$mapping$StatementType[ms.getStatementType().ordinal()]) {
        case 1:
            this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case 2:
            this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case 3:
            this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }

    }
    public abstract class BaseStatementHandler implements StatementHandler {
    protected final Configuration configuration;
    protected final ObjectFactory objectFactory;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final ResultSetHandler resultSetHandler;
    protected final ParameterHandler parameterHandler;
    protected final Executor executor;
    protected final MappedStatement mappedStatement;
    protected final RowBounds rowBounds;
    protected BoundSql boundSql;

    protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        this.configuration = mappedStatement.getConfiguration();
        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
        this.objectFactory = this.configuration.getObjectFactory();
        if(boundSql == null) {
            this.generateKeys(parameterObject);
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }

        this.boundSql = boundSql;
        this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);
    }
🎜Die SQL-Paging-Anweisung lautet wie folgt: select * from table limit index, pageSize;🎜🎜So wird currIndex im Dienst berechnet: der Index des ersten Datensatzes, der mit der Abfrage beginnt. 🎜🎜Test: 🎜🎜Geben Sie http://localhost:8080/student/sql/1/2 in den Browser ein, um die Daten auf der ersten Seite abzurufen. Daraufhin werden zwei Datenelemente angezeigt jede Seite. 🎜🎜Ergebnis: 🎜

Mehrere Möglichkeiten zum Paging in Mybatis

从输出结果可以看出和数组分页的结果是一致的,因此sql语句的分页也是没问题的。

缺点:虽然这里实现了按需查找,每次检索得到的是指定的数据。但是每次在分页的时候都需要去编写limit语句,很冗余。而且不方便统一管理,维护性较差。所以我们希望能够有一种更方便的分页实现。

3、拦截器分页

上面提到的数组分页和sql语句分页都不是我们今天讲解的重点,今天需要实现的是利用拦截器达到分页的效果。自定义拦截器实现了拦截所有以ByPage结尾的查询语句,并且利用获取到的分页相关参数统一在sql语句后面加上limit分页的相关语句,一劳永逸。不再需要在每个语句中单独去配置分页相关的参数了。。

首先我们看一下拦截器的具体实现,在这里我们需要拦截所有以ByPage结尾的所有查询语句,因此要使用该拦截器实现分页功能,那么再定义名称的时候需要满足它拦截的规则(以ByPage结尾),如下所示:

package com.cbg.interceptor;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

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

/**
 * Created by chenboge on 2017/5/7.
 * <p>
 * Email:baigegechen@gmail.com
 * <p>
 * description:
 */

/**
 * @Intercepts 说明是一个拦截器
 * @Signature 拦截器的签名
 * type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
 * method 拦截的方法
 * args 参数
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MyPageInterceptor implements Interceptor {

//每页显示的条目数
    private int pageSize;
//当前现实的页数
    private int currPage;

    private String dbType;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取StatementHandler,默认是RoutingStatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //获取statementHandler包装类
        MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);

        //分离代理对象链
        while (MetaObjectHandler.hasGetter("h")) {
            Object obj = MetaObjectHandler.getValue("h");
            MetaObjectHandler = SystemMetaObject.forObject(obj);
        }

        while (MetaObjectHandler.hasGetter("target")) {
            Object obj = MetaObjectHandler.getValue("target");
            MetaObjectHandler = SystemMetaObject.forObject(obj);
        }

        //获取连接对象
        //Connection connection = (Connection) invocation.getArgs()[0];


        //object.getValue("delegate");  获取StatementHandler的实现类

        //获取查询接口映射的相关信息
        MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");
        String mapId = mappedStatement.getId();

        //statementHandler.getBoundSql().getParameterObject();

        //拦截以.ByPage结尾的请求,分页功能的统一实现
        if (mapId.matches(".+ByPage$")) {
            //获取进行数据库操作时管理参数的handler
            ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
            //获取请求时的参数
            Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
            //也可以这样获取
            //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();

            //参数名称和在service中设置到map中的名称一致
            currPage = (int) paraObject.get("currPage");
            pageSize = (int) paraObject.get("pageSize");

            String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");
            //也可以通过statementHandler直接获取
            //sql = statementHandler.getBoundSql().getSql();

            //构建分页功能的sql语句
            String limitSql;
            sql = sql.trim();
            limitSql = sql + " limit " + (currPage - 1) * pageSize + "," + pageSize;

            //将构建完成的分页sql语句赋值个体&#39;delegate.boundSql.sql&#39;,偷天换日
            MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
        }
//调用原对象的方法,进入责任链的下一级
        return invocation.proceed();
    }


    //获取代理对象
    @Override
    public Object plugin(Object o) {
    //生成object对象的动态代理对象
        return Plugin.wrap(o, this);
    }

    //设置代理对象的参数
    @Override
    public void setProperties(Properties properties) {
//如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
        String limit1 = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limit1);
        this.dbType = properties.getProperty("dbType", "mysql");
    }
}

上面即是拦截器功能的实现,在intercept方法中获取到select标签和sql语句的相关信息,拦截所有以ByPage结尾的select查询,并且统一在查询语句后面添加limit分页的相关语句,统一实现分页功能。

重点详解:

StatementHandler是一个接口,而我们在代码中通过StatementHandler statementHandler = (StatementHandler) invocation.getTarget();获取到的是StatementHandler默认的实现类RoutingStatementHandler。而RoutingStatementHandler只是一个中间代理,他不会提供具体的方法。那你可能会纳闷了,拦截器中基本上是依赖statementHandler获取各种对象和属性的,没有具体属性和方法怎么行??接着看下面代码:

private final StatementHandler delegate;

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        switch(RoutingStatementHandler.SyntheticClass_1.$SwitchMap$org$apache$ibatis$mapping$StatementType[ms.getStatementType().ordinal()]) {
        case 1:
            this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case 2:
            this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case 3:
            this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }

    }

原来它是通过不同的MappedStatement创建不同的StatementHandler实现类对象处理不同的情况。这里的到的StatementHandler实现类才是真正服务的。看到这里,你可能就会明白MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");中delegate的来源了吧。至于为什么要这么去获取,后面我们会说道。

拿到statementHandler后,我们会通过MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);去获取它的包装对象,通过包装对象去获取各种服务。

MetaObject:mybatis的一个工具类,方便我们有效的读取或修改一些重要对象的属性。四大对象(ResultSetHandler,ParameterHandler,Executor和statementHandler)提供的公共方法很少,要想直接获取里面属性的值很困难,但是可以通过MetaObject利用一些技术(内部反射实现)很轻松的读取或修改里面的数据。

接下来说说:MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");

上面提到为什么要这么去获取MappedStatement对象??在RoutingStatementHandler中delegate是私有的(private final StatementHandler delegate;),有没有共有的方法去获取。所以这里只有通过反射来获取啦。

MappedStatement是保存了xxMapper.xml中一个sql语句节点的所有信息的包装类,可以通过它获取到节点中的所有信息。在示例中我们拿到了id值,也就是方法的名称,通过名称区拦截所有需要分页的请求。

通过StatementHandler的包装类,不光能拿到MappedStatement,还可以拿到下面的数据:

    public abstract class BaseStatementHandler implements StatementHandler {
    protected final Configuration configuration;
    protected final ObjectFactory objectFactory;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final ResultSetHandler resultSetHandler;
    protected final ParameterHandler parameterHandler;
    protected final Executor executor;
    protected final MappedStatement mappedStatement;
    protected final RowBounds rowBounds;
    protected BoundSql boundSql;

    protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        this.configuration = mappedStatement.getConfiguration();
        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
        this.objectFactory = this.configuration.getObjectFactory();
        if(boundSql == null) {
            this.generateKeys(parameterObject);
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }

        this.boundSql = boundSql;
        this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);
    }

上面的所有数据都可以通过反射拿到。

几个重要的参数: 

  • Configuration:所有配置的相关信息。 

  • ResultSetHandler:用于拦截执行结果的组装。 

  • ParameterHandler:拦截执行Sql的参数的组装。 

  • Executor:执行Sql的全过程,包括组装参数、组装结果和执行Sql的过程。 

  • BoundSql:执行的Sql的相关信息。

接下来我们通过如下代码拿到请求时的map对象(反射)。

 //获取进行数据库操作时管理参数的handler
            ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
            //获取请求时的参数
            Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
            //也可以这样获取
            //paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();

拿到我们需要的currPage和pageSize参数后,就是组装分页查询的sql语句’limitSql‘了。

最后通过MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);将原始的sql语句替换成我们新的分页语句,完成偷天换日的功能,接下来让代码继续执行。

编写好拦截器后,需要注册到项目中,才能发挥它的作用。在mybatis的配置文件中,添加如下代码:

    <plugins>
        <plugin interceptor="com.cbg.interceptor.MyPageInterceptor">
            <property name="limit" value="10"/>
            <property name="dbType" value="mysql"/>
        </plugin>
    </plugins>

如上所示,还能在里面配置一些属性,在拦截器的setProperties方法中可以获取配置好的属性值。如项目分页的pageSize参数的值固定,我们就可以配置在这里了,以后就不需要每次传入pageSize了,读取方式如下:

 //读取配置的代理对象的参数
    @Override
    public void setProperties(Properties properties) {
        String limit1 = properties.getProperty("limit", "10");
        this.pageSize = Integer.valueOf(limit1);
        this.dbType = properties.getProperty("dbType", "mysql");
    }

到这里,有关拦截器的相关知识就讲解的差不多了,接下来就需要测试,是否我们这样写真的有效??

首先还是添加dao层的方法和xml文件的sql语句配置,注意项目中拦截的是以ByPage结尾的请求,所以在这里,我们的方法名称也以此结尾:

方法
List<Student> queryStudentsByPage(Map<String,Object> data);

xml文件的select语句
    <select id="queryStudentsByPage" parameterType="map" resultMap="studentmapper">
        select * from student
    </select>

可以看出,这里我们就不需要再去手动配置分页语句了。

接下来是service层的接口编写和实现方法:

方法:
List<Student> queryStudentsByPage(int currPage,int pageSize);

实现:
 @Override
    public List<Student> queryStudentsByPage(int currPage, int pageSize) {
        Map<String, Object> data = new HashedMap();
        data.put("currPage", currPage);
        data.put("pageSize", pageSize);
        return studentMapper.queryStudentsByPage(data);
    }

这里我们虽然传入了currPage和pageSize两个参数,但是在sql的xml文件中并没有使用,直接在拦截器中获取到统一使用。

最后编写controller的测试代码:

 @ResponseBody
    @RequestMapping("/student/page/{currPage}/{pageSize}")
    public List<Student> getStudentByPage(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
        List<Student> student = StuServiceIml.queryStudentsByPage(currPage, pageSize);
        return student;
    }

测试: 

在浏览器输入:http://localhost:8080/student/student/page/1/2

结果: 

Mehrere Möglichkeiten zum Paging in Mybatis
可见和上面两种分页的效果是一样的。

4、RowBounds实现分页

原理:通过RowBounds实现分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。

存在问题:一次性从数据库获取的数据可能会很多,对内存的消耗很大,可能导师性能变差,甚至引发内存溢出。

适用场景:在数据量很大的情况下,建议还是适用拦截器实现分页效果。RowBounds建议在数据量相对较小的情况下使用。

简单介绍:这是代码实现上最简单的一种分页方式,只需要在dao层接口中要实现分页的方法中加入RowBounds参数,然后在service层通过offset(从第几行开始读取数据,默认值为0)和limit(要显示的记录条数,默认为java允许的最大整数:2147483647)两个参数构建出RowBounds对象,在调用dao层方法的时,将构造好的RowBounds传进去就能轻松实现分页效果了。

具体操作如下:

dao层接口方法:

//加入RowBounds参数
public List<UserBean> queryUsersByPage(String userName, RowBounds rowBounds);

然后在service层构建RowBounds,调用dao层方法:

  @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.SUPPORTS)
    public List<RoleBean> queryRolesByPage(String roleName, int start, int limit) {
        return roleDao.queryRolesByPage(roleName, new RowBounds(start, limit));
    }

RowBounds就是一个封装了offset和limit简单类,如下所示:

public class RowBounds {
    public static final int NO_ROW_OFFSET = 0;
    public static final int NO_ROW_LIMIT = 2147483647;
    public static final RowBounds DEFAULT = new RowBounds();
    private int offset;
    private int limit;

    public RowBounds() {
        this.offset = 0;
        this.limit = 2147483647;
    }

    public RowBounds(int offset, int limit) {
        this.offset = offset;
        this.limit = limit;
    }

    public int getOffset() {
        return this.offset;
    }

    public int getLimit() {
        return this.limit;
    }
}

只需要这两步操作,就能轻松实现分页效果了,是不是很神奇。但却不简单,内部是怎么实现的??给大家提供一个简单的思路:RowBounds分页简单原理

结论:从上面四种sql分页的实现方式可以看出,通过RowBounds实现是最简便的,但是通过拦截器的实现方式是最优的方案。只需一次编写,所有的分页方法共同使用,还可以避免多次配置时的出错机率,需要修改时也只需要修改这一个文件,一劳永逸。而且是我们自己实现的,便于我们去控制和增加一些逻辑处理,使我们在外层更简单的使用。同时也不会出现数组分页和RowBounds分页导致的性能问题。当然,具体情况可以采取不同的解决方案。数据量小时,RowBounds不失为一种好办法。但是数据量大时,实现拦截器就很有必要了。

【相关推荐:编程教学

Das obige ist der detaillierte Inhalt vonMehrere Möglichkeiten zum Paging in Mybatis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn