Mybatis流式查询
什么是流式查询
在处理大量数据时,普通查询会把所有数据一次性加载到内存中,这可能会导致内存溢出。而流式查询允许我们逐条处理数据,有效降低内存占用。
前提准备
-- 用户表结构
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 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接口
@Repository
public interface UserMapper {
/**
* 使用ResultHandler方式查询数据
*/
void listByResultHandler(ResultHandler<User> resultHandler);
}
实现
@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接口提供了以下主要方法:
isOpen()
: 判断游标是否打开isConsumed()
: 判断游标是否已经遍历完成getCurrentIndex()
: 获取当前游标位置(从0开始)close()
: 关闭游标,释放资源iterator()
: 返回一个迭代器用于遍历数据
xml配置
在上面的xml配置中使用了两个重要参数:
resultSetType
- FORWARD_ONLY: 结果集的游标只能向前移动
- SCROLL_SENSITIVE: 结果集的游标可以上下移动,当数据库变化时,当前结果集同步改变
- SCROLL_INSENSITIVE: 结果集的游标可以上下移动,但数据库变化时,当前结果集不变
fetchSize
fetchSize参数用于控制每次从数据库获取的记录数量,较小的值会减少内存占用但增加数据库交互次数,较大的值则相反。该参数默认值由JDBC驱动决定,在实际应用中建议根据数据量和系统内存情况设置在100-1000之间较为合适。
<?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接口
@Repository
public interface UserMapper {
Cursor<User> listByCorsor();
}
实现
需要保持在事务中,否则会报错断开连接。
@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...