流式查询指的是查询结果是一个迭代器,而不是一个集合,应用每次从迭代器查询一条结果。流式查询的好处是降低内存的使用。
但是要注意流式查询的过程中要保持连接一直存在,所以流式查询以后数据库访问框架就不负责关闭数据库连接了,需要自己去维护。
流式查询接口
package org.apache.ibatis.cursor;
import java.io.Closeable;
/**
* Cursor contract to handle fetching items lazily using an Iterator.
* Cursors are a perfect fit to handle millions of items queries that would not normally fits in memory.
* If you use collections in resultMaps then cursor SQL queries must be ordered (resultOrdered="true")
* using the id columns of the resultMap.
*
* @author Guillaume Darmont / guillaume@dropinocean.com
*/
public interface Cursor<T> extends Closeable, Iterable<T> {
/**
* @return true if the cursor has started to fetch items from database.
*/
boolean isOpen();
/**
*
* @return true if the cursor is fully consumed and has returned all elements matching the query.
*/
boolean isConsumed();
/**
* Get the current item index. The first item has the index 0.
* @return -1 if the first cursor item has not been retrieved. The index of the current item retrieved.
*/
int getCurrentIndex();
}
mybatis接口流式查询提供Cursor接口,由此可知:
- Cursor 是可以关闭的。实际上Cursor关闭时,数据库连接也一并关闭了。
- Cursor是可遍历的
---------------------------------------------mapper-----------------------------------------------
public interface TestMapper {
Cursor<User> getUserList();
}
---------------------------------------------service-----------------------------------------------
@Autowired
private TestMapper testMapper;
public void demoService(){
Cursor<User> cursor = testMapper.getUserList();
for (User user : cursor) {
}
}
还有一种写法
try(Cursor cursor = mapper.querySomeData()) {
cursor.forEach(rowObject -> {
// ...
});
}
使用 try-resource 方式可以令 Cursor 自动关闭。
流式查询构建方式
上面那段代码,构建mapper方法,返回类型是Cursor, @Autowired自动注入mapper,然后遍历结果,逻辑上是没有问题的。当我们将mapper返回参数定为Cursor,Mybatis就明白这是一个流式查询。这么些语法上没有任何问题,但是运行会报异常。
java.lang.IllegalStateException: A Cursor is already closed.
原因:Mapper执行完成之后,连接就关闭了,所以Cursor也就一并关闭了。
解决问题的核心就是让数据库的连接保持
参考网上的文章有3种方式:
- 方法一:SqlSessionFactory
用SqlSessionFactory手动打开连接,代码修改如下
@Autowired
private SqlSessionFactory sqlSessionFactory;
public void demoService() throws Exception{
try(
SqlSession sqlSession = sqlSessionFactory.openSession();
Cursor<User> userList = sqlSession.getMapper(TestMapper.class).getUserList();
) {
userList.forEach(listItem->{
});
}
}
这次需要我们手动关闭session连接
- 方法二:TransactionTemplate
在 Spring 中,我们可以用 TransactionTemplate 来执行一个数据库事务,这个过程中数据库连接同样是打开的。代码如下:
@Autowired
private TestMapper testMapper;
@Autowired
private PlatformTransactionManager platformTransactionManager;
public void demoService() throws Exception{
TransactionTemplate template = new TransactionTemplate( platformTransactionManager);
List<User> list = template.execute( status -> {
try (
Cursor<User> userList = testMapper.getUserList();
) {
List<User> users = new ArrayList<>();
userList.forEach(item->{
users.add(item);
});
return users;
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
}
- 方法三:@Transactional 注解
@Transactional
public void demoService() throws Exception{
try (
Cursor<User> userList = testMapper.getUserList();
) {
List<User> users = new ArrayList<>();
userList.forEach(item->{
users.add(item);
});
} catch (IOException e) {
e.printStackTrace();
}
}
使用注解注意,这个和异步执行标识@async一样,同一个类中方法调用是不起作用的,就是和demoService在同一个类中的另一个方法调用了这个方法, @Transactional注解不起作用
Mybatis - plus 的实现方式
-----------------------------------------------------------------mapper-----------------------------------------------
@Select("${sql}")
//这个注解是设定每次流式查询的iterator大小的,这里是1000条
//ResultSetType.FORWARD_ONLY 只允许游标向下移动
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
//这个注解是标明返回结果集类型的
@ResultType(Map.class)
void dynamicSelectLargeData(@Param("sql") String sql, ResultHandler<Map> handler);
-------------------------------------------------------------------service-----------------------------------------------
public void dynamicSelectLargeData(String sql) {
//sql是查询的sql语句,resultContext是ResultHandler类型,也就是mapper查询结果,这种方式不需要注解和自己创建session
dynamicMapper.dynamicSelectLargeData(sql,
resultContext -> {
Map one = (Map) resultContext.getResultObject();
//接着处理流式查询结果
System.out.println(one);
});
}