Mybatis 是一款优秀的 ORM 框架,它能够帮助程序员快速、简单地完成 Java 对象与关系数据库的互相映射。它提供了各种查询方式,其中一种比较有特色的查询方式就是使用 ResultHandler 进行查询,实现结果流式输出。
有一种场景,查询结果有上百万条数据,如果在查询时把整个结果集一次性查询得到,存储于 List 返回的话,很可能会因为结果集太大,导致内存溢出(OOM)。此时我们可以采用分页查询或者可以利用 Mybatis 中的 ResultHandler 来实现流式输出。
ResultHandler 接口只有一个方法 handleResult,我们可以实现这个方法去处理每一条 SQL 查询返回的数据。下面将详细介绍 Mybatis 中 ResultHandler 的用法。
一、ResultHandler 简介
Mybatis 中的 ResultHandler 相当于数据结果集的处理器,它是一个回调函数(Callback),用来处理每一行数据的结果,这个回调函数可以在查询结果处理到一定量时触发,对结果集数据进行定制化处理。
ResultHandler 的使用可以大幅提升数据处理的效率,当我们需要处理大量的数据时,一般会使用 ResultHandler 来进行结果的处理,避免一次查询就全部返回结果,浪费内存资源或造成 OOM。
二、ResultHandler 实现结果流式输出(两种写法)
2.1 创建 TestResultHandler 类,实现 ResultHandler 接口
创建 TestResultHandler 类,实现 ResultHandler 接口,重写 handleResult 方法,实现具体的业务逻辑,例如简单的打印或输出到文件等。其中,TblTestUser 是映射数据库表格的实体类。
或者不创建该类,在查询时直接创建 ResultHandler 类,具体见 2.4 小节中的两种服务层写法。
package com.test.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import com.test.model.TblTestUser;
@Slf4j
public class TestResultHandler implements ResultHandler<TblTestUser> {
@Override
public void handleResult(ResultContext<? extends TblTestUser> resultContext) {
TblTestUser tblTestUser = resultContext.getResultObject();
// 处理数据的逻辑
try {
// 打印每行日志
log.info("userNo : [{}]", tblTestUser.getUserNo());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2.2 TblTestUserMapper 接口,定义查询方法
package com.test.dao;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ResultHandler;
public interface TblTestUserMapper {
// SQL 返回值为 void,所以我们并没有接收这个返回值。所以不会产生大对象
// 方法中的第一个参数是查询时的限制条件,第二个是结果处理器
void selectByResultHandler(@Param(value = "actNo") String actNo,
ResultHandler resultHandler);
}
2.3 XML 语法
xml 查询方法和常规查询方法写法一致。
<select id="selectByResultHandler" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tbl_test_user
</select>
2.4 服务层写法
package com.test.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.test.dao.TblTestUserMapper;
import com.test.model.TblTestUser;
import javax.annotation.Resource;
@Service
@Slf4j
public class TestService {
@Resource
private TblTestUserMapper tblTestUserMapper;
/**
* 写法 1
*
* @param actNo 筛选条件:活动号
*/
@Async
public void test1(String actNo) {
log.info("ResultHandler1 处理查询结果");
TestResultHandler testResultHandler = new TestResultHandler();
tblTestUserMapper.selectByResultHandler(actNo, testResultHandler);
}
/**
* 写法 2
*
* @param actNo 筛选条件:活动号
*/
@Async
public void test2(String actNo) {
log.info("ResultHandler2 处理查询结果");
tblTestUserMapper.selectByResultHandler(actNo, new ResultHandler<TblTestUser>() {
@Override
public void handleResult(ResultContext<? extends TblTestUser> resultContext) {
TblTestUser tblTestUser = resultContext.getResultObject();
// 处理数据的逻辑
try {
// 打印每行日志
log.info("userNo : [{}]", tblTestUser.getUserNo());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
}