Skip to content

Mybatis流式查询

什么是流式查询

在处理大量数据时,普通查询会把所有数据一次性加载到内存中,这可能会导致内存溢出。而流式查询允许我们逐条处理数据,有效降低内存占用。

前提准备

sql
-- 用户表结构
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `code` varchar(128) NOT NULL COMMENT '编码',
  `nickname` varchar(64) NOT NULL COMMENT '昵称',
  `username` varchar(32) NOT NULL COMMENT '用户名',
  `password` varchar(128) NOT NULL COMMENT '密码',
  `phone` varchar(11) DEFAULT NULL COMMENT '手机号',
  `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
  `avatar` varchar(128) DEFAULT NULL COMMENT '头像',
  `like` varchar(64) DEFAULT NULL COMMENT '爱好',
  `desc` varchar(100) DEFAULT NULL COMMENT '描述',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态',
  `is_deleted` tinyint NOT NULL DEFAULT 0 COMMENT '删除标记',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` int NOT NULL DEFAULT 1 COMMENT '创建人',
  `update_user` int NOT NULL DEFAULT 1 COMMENT '更新人',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

生成了10000条数据,用于模拟大数据量查询。

实现方式

1. 使用ResultHandler

使用ResultHandler是最简单的流式查询方式。ResultHandler接口允许我们一条一条地处理查询结果。

xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openyuan.mapper.UserMapper">
    
    <!-- listByResultHandler --> 
    <select id="listByResultHandler" resultType="com.openyuan.entity.User" resultSetType="FORWARD_ONLY" fetchSize="100">
        select * from user
    </select>
</mapper>

Mapper接口

java
@Repository
public interface UserMapper {

    /**
     * 使用ResultHandler方式查询数据
     */
    void listByResultHandler(ResultHandler<User> resultHandler);
}

实现

java
@Service
public class UserServiceImpl implements UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    
    @Resource
    private UserMapper userMapper;

    @Override
    public void listByResultHandler() {
        userMapper.listByResultHandler(new ResultHandler<User>() {
            @Override
            public void handleResult(ResultContext<? extends User> resultContext) {
                logger.info("第 {} 条, 读取到一条用户数据 ==> {}", resultContext.getResultCount(), resultContext.getResultObject().toString());
            }
        });
    }
}

运行结果(部分输出)

第 996 条, 读取到一条用户数据 ==> User(id=13526999... 第 997 条, 读取到一条用户数据 ==> User(id=13527000... 第 998 条, 读取到一条用户数据 ==> User(id=13527001...

2. 使用Cursor

Cursor是一种更高级的流式查询方式,它允许我们逐条处理数据,同时支持批量处理。

Cursor接口提供了以下主要方法:

  1. isOpen(): 判断游标是否打开

  2. isConsumed(): 判断游标是否已经遍历完成

  3. getCurrentIndex(): 获取当前游标位置(从0开始)

  4. close(): 关闭游标,释放资源

  5. iterator(): 返回一个迭代器用于遍历数据

xml配置

在上面的xml配置中使用了两个重要参数:

resultSetType

  • FORWARD_ONLY: 结果集的游标只能向前移动
  • SCROLL_SENSITIVE: 结果集的游标可以上下移动,当数据库变化时,当前结果集同步改变
  • SCROLL_INSENSITIVE: 结果集的游标可以上下移动,但数据库变化时,当前结果集不变

fetchSize

fetchSize参数用于控制每次从数据库获取的记录数量,较小的值会减少内存占用但增加数据库交互次数,较大的值则相反。该参数默认值由JDBC驱动决定,在实际应用中建议根据数据量和系统内存情况设置在100-1000之间较为合适。

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openyuan.mapper.UserMapper">
    
    <!-- listByCorsor --> 
    <select id="listByCorsor" resultType="com.openyuan.entity.User" resultSetType="FORWARD_ONLY" fetchSize="100" >
        select * from user
    </select>
</mapper>

Mapper接口

java
@Repository
public interface UserMapper {
    
    Cursor<User> listByCorsor();
}

实现

需要保持在事务中,否则会报错断开连接。

java
@Service
public class UserServiceImpl implements UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    
    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void listByCorsor() {
        // 使用游标方式查询数据
        try (Cursor<User> cursor = userMapper.listByCorsor()) {
            for (User user : cursor) {
                logger.info("第 {} 条, 读取到一条用户数据 ==> {}", cursor.getCurrentIndex() + 1, user.toString());
            }
        } catch (Exception e) {
            logger.error("查询用户数据失败", e);
        }
    }
}

运行结果(部分输出)

第 996 条, 读取到一条用户数据 ==> User(id=13526999...

第 997 条, 读取到一条用户数据 ==> User(id=13527000...

第 998 条, 读取到一条用户数据 ==> User(id=13527001...

Demo项目地址

https://github.com/Jacqueline712/mybatis-demo