MyBatis流式查询
大约 3 分钟
mybatis的流式查询:通过 mybatis 执行查询数据的请求执行成功后,mybatis 返回的结果集不是一个集合或对象,而是一个迭代器,可以通过遍历迭代器来取出结果集,避免一次性取出大量的数据而占用太多的内存。
使用流式查询,服务端程序在查询数据的过程中,与远程数据库一直保持连接,不断的去数据库拉取数据,提交事务并关闭sqlsession后,数据库连接断开,停止数据拉取。
- 使用流式查询,需要自己手动维护sqlsession和事务的提交。
mybatis的流式查询,有点冷门,实际用的场景比较少,但是在某些特殊场景下,却是十分有效的一个方法。
Cursor 迭代器
// org.apache.ibatis.cursor.Cursor
public interface Cursor<T> extends Closeable, Iterable<T> {
// 判断cursor是否正处于打开状态
// 当返回true,则表示cursor已经开始从数据库里刷新数据了;
boolean isOpen();
// 判断查询结果是否全部读取完;
// 当返回true,则表示查询sql匹配的全部数据都消费完了;
boolean isConsumed();
// 查询已读取数据在全部数据里的索引位置;
// 第一条数据的索引位置为0;当返回索引位置为-1时,则表示已经没有数据可以读取;
int getCurrentIndex();
}代码实现
Mapper 接口中的查询方法,返回值类型为 Cursor<...> 皆可,sql不用改。
public interface PersonMapper {
Cursor<Person> selectByCursor();
Integer queryCount();
}使用案例:
// 使用sqlSessionFactory打开一个sqlSession,在没有读取完数据之前不要提交事务或关闭sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 获取到指定mapper
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
// 调用指定mapper的方法,返回一个cursor
Cursor<Person> cursor = mapper.selectByCursor();
// 查询数据总量
Integer total = mapper.queryCount();
// 定义一个list,用来从cursor中读取数据,每读取够1000条的时候,开始处理这批数据;
// 当前批数据处理完之后,清空list,准备接收下一批次数据;直到大量的数据全部处理完;
List<Person> personList = new ArrayList<>();
int i = 0; // 组序
if (cursor != null) {
for (Person person : cursor) {
if (personList.size() < 1000) {
personList.add(person); // 组内不满1000,直接加
} else if (personList.size() == 1000) {
++i; // 组内已满1000,编组,开始处理
// dosome...
personList.clear();
personList.add(person); // 把当前遍历的对象放入新租中
}
if (total == (cursor.getCurrentIndex() + 1)) {
++i; // 已取完数据,直接编组,开始处理
// dosome...
personList.clear();
}
}
if (cursor.isConsumed()) {
// 处理完的收尾
// dosome...
}
}
sqlSession.commit(); // 手动管理事务
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}mybatis的流式查询的本意,是避免大量数据的查询而导致内存溢出,因此dao层查询返回的是一个迭代器(Cursor),这样做的最大好处就是能够降低内存使用和垃圾回收器的负担,使数据处理的过程相对更加高效、可控,内存溢出的风险较小。
但是缺点也很就明显,处理的时间可能会变长,需要引入多线程异步操作,并且在迭代器遍历和数据处理的过程中,数据库连接不能断开,即当前sqlSession要保持持续打开状态,一量断开,数据读取就会中断,所以关于这块的处理,使用mybatis原生的sqlSession进行手动查询、提交事务、回滚和关闭sqlSession最为稳妥、最简单。
