【小笔记】MyBaits的Cursor

流式查询

流式查询 指的是查询成功后不是返回一个集合而是返回一个迭代器,应用可以通过迭代器每次取一条查询结果。流式查询的好处是能够降低内存使用。

例如我们想要从数据库取 1000 万条记录而又没有足够的内存时,就不得不分页查询。而分页查询就需要我们按照顺序查询并设置一个参数来记录当前进度并在下次查询时将进度作为参数传入(比如按id升序查询,记录每次查询结果的最大id, 下次查询将这个最大id传入只查询大于这个id的),否则就会出现深度分页的情况。而查询效率取决于表设计,如果设计的不好,那么每次查询都会是一次单独的低效查询。

而流式查询不需要自己记录进度(数据库来记录),且即使表设计的较差或在sql比较复杂,也仅仅只需要一次低效查询。

流式查询的过程当中,要保证数据库连接是保持打开状态的,否则就会流关闭。

MyBaits通过游标Cursor实现了流式查询。 MyBaits Plus基于Mybais, 自然也是支持的。

如何使用

写一个获取流的Mapper

不需要其他配置, 像平常我们写查询一样在Mapper定义查询,并将返回结果设为Cursor即可实现一个流式查询。

sql也正常按照时间查询条件写,不需要加limit之类的参数。

java 复制代码
Cursor<MyDbTableDo> queryByCursor()

获取一个打开状态的连接。

上面提过,使用流式查询需要保证数据库连接是保持打开状态。而正常情况下我们使用mybaits执行一次查询,连接都会被关闭或在重置。因此我们需要一些方法来保持连接。

使用事务

事务执行完毕之前连接会一直保持因此,我们可以来使用事务来保持连接。

@Transactional 注解
java 复制代码
@Transactional
public void transactionalAnno() throws Exception {
    try (Cursor<MyDbTableDo> cursor = myDbTableMapper.queryByCursor()) {
        cursor.forEach(item -> { });
    }
}

这是最简单的方法,但是需要注意的是,由于cursor在遍历结束的方法末尾后会主动关闭连接。因此:

  1. 方法内事务是正常的,在cursor查询前和遍历后的的数据操作依然是一个事务。从这里可以猜测,连接并不在在遍历完立即关闭,而是采用了类似AOP的手段在方法末尾关闭。
  2. 如果你使用了数据库连接池,那么池中的这个连接会被关掉,这可能并不会导致你的连接池数量减少,因为连接池的连接数据依赖于各自的规则,当有连接断开,连接池会根据自己的策略对做相应的处理,比如重新建立,因此不需要过分关注这个,需要注意的是这个行为可能带来的影响,比如druid连接池,druid在事务执行完毕后会进行连接的清理。但是这个连接在myDbTableMapper遍历完成时已经关闭,就会导致日志打印java.sql.SQLException: No operations allowed after statement closed。这个问题不会带来数据影响,但是依然属于一个错误,见github-druid-issues,在1.2.10, druid将这个日志改为debug级别进行了临时的屏蔽。其他连接池尚未进行测试。
TransactionTemplate开启事务
java 复制代码
public void transactionTemplate() throws Exception {
    TransactionTemplate transactionTemplate =
            new TransactionTemplate(transactionManager);  

    transactionTemplate.execute(status -> {              
        try (Cursor<MyDbTableDo> cursor = myDbTableMapper.queryByCursor()) {
            cursor.forEach(item -> { });
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    });
}

在 Spring 中,用 TransactionTemplate 来执行一个数据库事务,这个过程中数据库连接同样是打开的

SqlSessionFactory主动打开一个连接

SqlSessionFactory 来手工打开数据库连接,SqlSessionFactory直接注入即可,MyBatis维护了这个Bean.

java 复制代码
public void sqlSessionFactory() throws Exception {
    try (
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Cursor<MyDbTableDo> cursor = myDbTableMapper.queryByCursor();
    ) {
        cursor.forEach(item -> { });
    }
}

开启一个 SqlSession (实际上也代表了一个数据库连接),并保证它最后能关闭;然后使用 SqlSession 来获得 Mapper 对象。这样才能保证得到的 Cursor 对象是打开状态的。

循环获取数据

Cursor继承了迭代器,可以通过Cursor获取他的Iterator, 或者直接使用for循环来获取数据。

需要注意的是,Cursor在查询一瞬间数据就固化了,如果你先查询,再更新,再遍历,即使在同一个事务里面,获得的结果也是未更新的数据。

关闭流

cursor在遍历结束后会主动关闭连接。如果未遍历结束中途退出,可以调用cursor的close方法关闭连接。推荐finaly内总是调用close或者try(resoure)来保证连接总是被正常关闭。

参考资料:
你还在用分页?试试 MyBatis 流式查询,真心强大!

PS:
【JAVA核心知识】系列导航 [持续更新中...]

关联导航:MySQL架构基础

关联导航:MySQL数据类型选择与设计

关联导航:创建高性能的索引

关联导航:查询性能优化

关联导航:EXPLAIN的使用

关联导航:深度了解MySql的InnoDB引擎

欢迎关注...

相关推荐
学地理的小胖砸1 小时前
【Python 操作 MySQL 数据库】
数据库·python·mysql
dddaidai1231 小时前
Redis解析
数据库·redis·缓存
数据库幼崽2 小时前
MySQL 8.0 OCP 1Z0-908 121-130题
数据库·mysql·ocp
Amctwd2 小时前
【SQL】如何在 SQL 中统计结构化字符串的特征频率
数据库·sql
betazhou2 小时前
基于Linux环境实现Oracle goldengate远程抽取MySQL同步数据到MySQL
linux·数据库·mysql·oracle·ogg
lyrhhhhhhhh3 小时前
Spring 框架 JDBC 模板技术详解
java·数据库·spring
喝醉的小喵4 小时前
【mysql】并发 Insert 的死锁问题 第二弹
数据库·后端·mysql·死锁
付出不多5 小时前
Linux——mysql主从复制与读写分离
数据库·mysql
初次见面我叫泰隆5 小时前
MySQL——1、数据库基础
数据库·adb
Chasing__Dreams5 小时前
Redis--基础知识点--26--过期删除策略 与 淘汰策略
数据库·redis·缓存