如何用mybatis进行分页?

Posted

技术标签:

【中文标题】如何用mybatis进行分页?【英文标题】:How to do Pagination with mybatis? 【发布时间】:2013-07-04 21:08:24 【问题描述】:

我目前正在开发一个电子商务应用程序,我必须在其中使用搜索功能显示可用产品列表。

与每次搜索一样,我必须在这里实现分页。

我使用 mybatis 作为我的 ORM 工具,使用 mysql 作为底层数据库。

谷歌搜索我发现了以下方法来完成这项任务:

    客户端分页 :在这里,我将不得不一次性从数据库中获取与搜索条件匹配的所有结果,并在我的代码级别处理分页(可能是前端代码)。

    服务器端分页: 使用 mysql,我可以使用结果集的 Limit 和偏移量来构造如下查询: SELECT * FROM sampletable WHERE condition1>1 AND condition2>2 LIMIT 0,20

在这里,每次用户在搜索结果中导航时选择新页面时,我都必须传递偏移量和限制计数。

谁能告诉我,

    哪种方法更好地实现分页? mybatis 是否支持一种比仅仅依赖上述 SQL 查询(如休眠条件 API)更好的方式来实现分页。

高度赞赏任何输入。 谢谢。

【问题讨论】:

【参考方案1】:

如果您使用的是 Spring MyBatis,您可以使用 2 个 MyBatis 查询和有用的 Spring PagePageable 接口手动实现分页。

您创建更高级别的DAO 接口,例如UploadDao

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface UploadDao 

    Page<Upload> search(UploadSearch uploadSearch, Pageable pageable);


... 其中Upload 映射到upload 表,UploadSearch 是一个参数 POJO,例如

@Data  // lombok
public class UploadSearch 

    private Long userId;
    private Long projectId;
    ... 


UploadDao 的实现(注入一个 MyBatis UploadMapper 映射器)如下:

public class DefaultUploadDao implements UploadDao 

    @Autowired
    private UploadMapper uploadMapper;

    public Page<Upload> searchUploads(UploadSearch uploadSearch, Pageable pageable) 
        List<Upload> content = uploadMapper.searchUploads(uploadSearch, pageable);
        Long total = uploadMapper.countUploads(uploadSearch);
        return new PageImpl<>(content, pageable, total);
    


DAO 实现调用UploadMapper 的2 个方法。它们是:

    UploadMapper.searchUploads - 根据搜索参数 (UploadSearch) 和 Pageable 参数(包含偏移量/限制等)返回一页结果。 UploadMapper.countUploads - 返回总计数,再次基于搜索参数 UploadSearch。注意 - 这里不需要Pageable 参数,因为我们只是确定搜索参数过滤到的总行数,而不关心页码/偏移量等。

注入的UploadMapper接口看起来像...

@Mapper
public interface UploadMapper 

    List<Upload> searchUploads(
        @Param("search") UploadSearch search,
        @Param("pageable") Pageable pageable);

    long countUploads(
        @Param("search") UploadSearch search);


... 以及包含动态 SQL 的映射器 XML 文件,例如upload_mapper.xml 包含...

<mapper namespace="com.yourproduct.UploadMapper">

    <select id="searchUploads" resultType="com.yourproduct.Upload">
        select u.*
          from upload u
         <include refid="queryAndCountWhereStatement"/>
         <if test="pageable.sort.sorted">
             <trim prefix="order by">
                 <foreach item="order" index="i" collection="pageable.sort" separator=", ">
                     <if test="order.property == 'id'">id $order.direction</if>
                     <if test="order.property == 'projectId'">project_id $order.direction</if>
                 </foreach>
             </trim>
         </if>
        <if test="pageable.paged">
            limit #pageable.offset, #pageable.pageSize  
        </if>
        <!-- NOTE: PostgreSQL has a slightly different syntax to MySQL i.e. 
             limit #pageable.pageSize offset #pageable.offset 
        -->
    </select>

    <select id="countUploads" resultType="long">
        select count(1)
         from upload u
        <include refid="queryAndCountWhereStatement"/>
    </select>

    <sql id="queryAndCountWhereStatement">
        <where>
            <if test="search != null">
                <if test="search.userId != null"> and u.user_id = #search.userId</if>
                <if test="search.productId != null"> and u.product_id = #search.productId</if>
                ...
            </if>
        </where>
    </sql>
</mapper>

注意 - &lt;sql&gt; 块(连同 &lt;include refid=" ... " &gt;)在这里非常有用,可确保您的 countselect 查询对齐。此外,在排序时,我们使用条件,例如&lt;if test="order.property == 'projectId'"&gt;project_id $order.direction&lt;/if&gt; 映射到列(并停止 SQL 注入)。 $order.direction 是安全的,因为 Spring Direction 类是 enum

UploadDao 然后可以从例如注入和使用。一个 Spring 控制器:

@RestController("/upload")
public UploadController 

    @Autowired
    private UploadDao uploadDao;  // Likely you'll have a service instead (which injects DAO) - here for brevity

    @GetMapping
    public Page<Upload>search (@RequestBody UploadSearch search, Pageable pageable) 
        return uploadDao.search(search, pageable);
    


【讨论】:

【参考方案2】:

如果您使用 Mappers(比使用原始 SqlSessions 容易得多),应用限制的最简单方法是在映射函数的参数列表中添加 RowBounds 参数,例如:

// without limit
List<Foo> selectFooByExample(FooExample ex);

// with limit
List<Foo> selectFooByExample(FooExample ex, RowBounds rb);

这在 the link Volodymyr posted 中几乎是事后提及的,在 Using Mappers 标题下,并且可以更加强调:

您还可以将 RowBounds 实例传递给该方法以限制查询结果。

请注意,对 RowBounds 的支持可能因数据库而异。 Mybatis 文档暗示 Mybatis 将负责使用适当的查询。但是,至少对于 Oracle,这可以通过对数据库的非常低效的重复调用来处理。

【讨论】:

Rowbound是DB层分页还是ORM层分页?【参考方案3】:

分页有物理和逻辑两种类型

逻辑意味着首先检索所有数据,然后在内存中对它们进行排序 物理意味着数据库级子集选择

mybatis 默认的分页是合乎逻辑的...因此当您选择一个海量数据库时,例如 100GB 的 blob,rowbound 方法仍然会很慢

解决办法是使用物理分页

你可以通过mybatisinterceptor做你自己的方式 或使用别人预先制作的plugins

【讨论】:

【参考方案4】:

如果你使用的是 MyBatis Generator,你可能想试试官方网站上的 Row Bounds 插件:org.mybatis.generator.plugins.RowBoundsPlugin。这个插件将添加一个新版本的 selectByExample 接受 RowBounds 参数的方法。

【讨论】:

【参考方案5】:

我自己在 sql 查询中使用您的第二个选项与 LIMIT。

但是有许多方法支持使用 RowBounds 类进行分页。 这在 mybatis 文档here中有很好的描述

注意使用正确的结果集类型。

【讨论】:

以上是关于如何用mybatis进行分页?的主要内容,如果未能解决你的问题,请参考以下文章

三个类告诉你MyBatis是如何用动态代理实现的

mybatis插件机制

mybatis对mysql进行分页

mybatis如何实现分页功能?

MyBatis物理分页的代码实现

MyBatis使用pagehelper进行分页