Maison > Java > JavaBase > le corps du texte

Plusieurs façons de paginer dans mybatis

青灯夜游
Libérer: 2023-01-04 16:23:34
original
7100 Les gens l'ont consulté

Méthode de pagination Mybatis : 1. Utilisez des tableaux pour la pagination. Interrogez d'abord toutes les données, puis interceptez les parties requises de la liste. 2. Utilisez l'instruction SQL pour effectuer la pagination et ajoutez l'instruction de pagination limite après l'instruction sql. 3. Utilisez l'intercepteur pour la pagination et ajoutez l'instruction limit à la fin de l'instruction SQL via l'intercepteur pour effectuer des requêtes de pagination. 4. Pour utiliser RowBounds pour implémenter la pagination, vous devez obtenir toutes les données qualifiées à la fois, puis exploiter le Big Data en mémoire pour obtenir l'effet de pagination.

Plusieurs façons de paginer dans mybatis

L'environnement d'exploitation de ce tutoriel : système windows7, java8, ordinateur Dell G3.

Les méthodes de pagination mybatis peuvent être divisées en deux grandes catégories :

1. La pagination logique

2 La pagination physique

La méthode spécifique de pagination mybatis

  • La pagination à l'aide de tableaux (logiques). pagination)

  • Utilisez des instructions SQL pour la pagination (pagination physique)

  • Paging d'intercepteur (pagination physique) Utilisez l'intercepteur pour ajouter une instruction limit à la fin de l'instruction SQL pour interroger, une fois pour toutes, le meilleur

  • RowBounds implémente la pagination (pagination logique)

1. Pagination à l'aide de tableaux

Principe : lors de l'exécution d'une opération de requête de base de données, obtenez tous les enregistrements qui remplissent les conditions dans la base de données, enregistrez dans le tableau temporaire de l'application, puis utilisez la méthode subList de List pour obtenir tous les enregistrements qui remplissent les conditions.

Implémentation :

Tout d'abord, créez l'interface StudentMapper au niveau de la couche dao pour les opérations de base de données. Définissez la méthode de requête pour parcourir le tableau dans l'interface, comme indiqué ci-dessous :

 List<Student> queryStudentsByArray();
Copier après la connexion

La méthode est très simple, c'est-à-dire obtenir toutes les données, les recevoir via la liste, puis effectuer l'opération de pagination.

Créez le fichier StudentMapper.xml et écrivez l'instruction SQL de requête :

 <select id="queryStudentsByArray"  resultMap="studentmapper">
        select * from student
 </select>
Copier après la connexion

On peut voir que lors de l'écriture de l'instruction SQL, nous n'avons effectué aucune opération liée à la pagination. Toutes les informations sur les étudiants peuvent être trouvées ici.

Ensuite, obtenez les données dans la couche de service et implémentez la pagination :

Définissez l'interface IStuService et définissez la méthode de pagination :

List<Student> queryStudentsByArray(int currPage, int pageSize);
Copier après la connexion

En recevant le paramètre currPage, il indique quelle page de données est affichée, et pageSize indique le numéro des éléments de données affichés sur chaque page.

Créez la classe d'implémentation de l'interface IStuService StuServiceIml pour implémenter la méthode et paginez le tableau obtenu via currPage et 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);
    }
Copier après la connexion

Obtenez toutes les données entre les deux index via la méthode subList.

Enfin, créez une méthode de test dans le contrôleur :

  @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;
    }
Copier après la connexion

Obtenez les données spécifiées via currPage et pageSize transmis par l'utilisateur.

Test :

Nous obtenons d'abord toutes les données obtenues avant que l'effet de pagination ne soit implémenté, comme indiqué ci-dessous :

Plusieurs façons de paginer dans mybatis

Ensuite, entrez http://localhost:8080/student/ dans le navigateur student/ array/1/2Le test implémente les données de pagination. Obtenez les données sur la première page et affichez deux éléments de données sur chaque page. http://localhost:8080/student/student/array/1/2测试实现了分页后的数据。获取第一页的数据,每页显示两条数据。

结果如下:

Plusieurs façons de paginer dans mybatis 输出的是指定的从第0-2条数据,可见我们通过数组分页的功能是成功的。(这里因为用到了关联查询,所以看起来数据可能比较多)

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

2、借助Sql语句进行分页

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

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

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

List<Student> queryStudentsBySql(Map<String,Object> data);
Copier après la connexion

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

 <select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
        select * from student limit #{currIndex} , #{pageSize}
</select>
Copier après la connexion

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

List<Student> queryStudentsBySql(int currPage, int pageSize);
Copier après la connexion
 @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);
    }
Copier après la connexion

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

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

测试:

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

Les résultats sont les suivants :

2 .png La sortie correspond aux données spécifiées de 0 à 2, ce qui montre que notre fonction de pagination via le tableau a réussi. (Étant donné que des requêtes associées sont utilisées ici, il semble qu'il puisse y avoir beaucoup de données) 🎜🎜Inconvénients : La base de données interroge et renvoie toutes les données, et nous n'avons besoin que d'une très petite quantité de données qui répondent aux exigences. Lorsque la quantité de données est faible, cela est acceptable. Lorsque la quantité de données de la base de données est trop importante, chaque requête aura un impact important sur les performances de la base de données et du programme. 🎜🎜🎜🎜2. Utilisez les instructions SQL pour la pagination 🎜🎜🎜🎜Après avoir pris connaissance des inconvénients de la pagination dans les tableaux, nous avons constaté que nous ne pouvons pas récupérer toutes les données de la base de données à chaque fois. Effectuez ensuite des opérations secondaires sur la grande quantité de données obtenues dans le programme, ce qui consomme beaucoup d'espace et de performances. Par conséquent, nous espérons récupérer directement uniquement les enregistrements qui remplissent les conditions dans le langage de la base de données sans les traiter via le programme. À cette époque, la technologie de pagination des instructions SQL a vu le jour. 🎜🎜Implémentation : Il est également très simple d'implémenter la pagination via des instructions SQL. Il nous suffit de modifier l'instruction de notre requête pour y parvenir, c'est-à-dire d'ajouter une instruction de pagination limite après l'instruction sql. 🎜🎜Tout d'abord, ajoutez la méthode de requête d'instruction SQL dans l'interface StudentMapper, comme suit : 🎜
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");
    }
}
Copier après la connexion
Copier après la connexion
🎜 Ensuite, écrivez l'instruction SQL dans StudentMapper et implémentez la pagination SQL dans 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());
        }

    }
Copier après la connexion
Copier après la connexion
    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);
    }
Copier après la connexion
Copier après la connexion
🎜L'instruction de pagination SQL est la suivante : select * from table limit index, pageSize;🎜🎜Donc currIndex est calculé dans le service : l'index du premier enregistrement pour lequel l'interrogation est lancée. 🎜🎜Test : 🎜🎜Entrez http://localhost:8080/student/sql/1/2 dans le navigateur pour obtenir les données sur la première page, et deux éléments de données seront affichés sur chaque page. 🎜🎜Résultat : 🎜

Plusieurs façons de paginer dans 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");
    }
}
Copier après la connexion
Copier après la connexion

上面即是拦截器功能的实现,在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());
        }

    }
Copier après la connexion
Copier après la connexion

原来它是通过不同的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);
    }
Copier après la connexion
Copier après la connexion

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

几个重要的参数:

  • 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();
Copier après la connexion

拿到我们需要的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>
Copier après la connexion

如上所示,还能在里面配置一些属性,在拦截器的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");
    }
Copier après la connexion

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

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

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

xml文件的select语句
    <select id="queryStudentsByPage" parameterType="map" resultMap="studentmapper">
        select * from student
    </select>
Copier après la connexion

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

接下来是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);
    }
Copier après la connexion

这里我们虽然传入了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;
    }
Copier après la connexion

测试:

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

结果:

Plusieurs façons de paginer dans 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);
Copier après la connexion

然后在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));
    }
Copier après la connexion

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;
    }
}
Copier après la connexion

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

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

【相关推荐:编程教学

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal