正常访问数据库的查询操作,都是根据查询sql一次性返回查询结果。
但如果遇到目标数据量过大、且需要全量查询、不能分页、或者内存不想被返回的结果占用过多等需求时(例如导出excel),就可能需要流式查询。
注:idea必须配置build,否则扫描不到src下的xml文件
relife
org.example
1.0-SNAPSHOT
4.0.0
relife-object
log4j
log4j
1.2.17
junit
junit
4.12
compile
cglib
cglib
2.2
mysql
mysql-connector-java
8.0.20
org.mybatis
mybatis
3.4.6
src/main/java
**/*.xml
public class User {
private String name;
private int age;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
mybatis-config.xml(resource根目录)
userMapper.xml
public class UserResultHandler implements ResultHandler {
@Override
public void handleResult(ResultContext extends User> resultContext) {
// 这里获取流式查询每次返回的单条结果
User user = resultContext.getResultObject();
handle(user);
}
// 串行逐条执行handle
private void handle(User user) {
System.out.println(user.getId());
}
}
适用于导出excel等,,上述写法缺点也很明显,单线程,串行,所有效率慢,但是类似导出excel,符合要求,也不太方便使用多线程。
为了解决效率问题,有时候会对结果有比如发送请求,更行其他内容等需求时
public class UserResultHandler implements ResultHandler {
public final Logger logger = Logger.getLogger(this.getClass());
/**
* 线程池线程数
*/
private int threadPollNum = 100;
public UserResultHandler() {
}
public UserResultHandler(int threadPollNum) {
this.threadPollNum = threadPollNum;
}
// 线程池
public ExecutorService executorService = Executors.newFixedThreadPool(threadPollNum);
// 线程执行结果
public List futureList = new ArrayList<>();
@Override
public void handleResult(ResultContext extends T> resultContext) {
// 这里获取流式查询每次返回的单条结果
T user = resultContext.getResultObject();
while (futureList.size() > 200) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
futureList = futureList.stream().filter(future -> !future.isDone()).collect(Collectors.toList());
logger.info("条数:" + resultContext.getResultCount() + "->未完成结果" + futureList.size());
}
UserThread ut = new UserThread(user);
Future> future = executorService.submit(ut);
futureList.add(future);
}
/**
* 保证所有线程执行完成,并关闭线程池
*/
public void end() {
while (futureList.size() != 0) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
futureList = futureList.stream().filter(future -> !future.isDone()).collect(Collectors.toList());
}
executorService.shutdown();
}
public static class UserThread implements Runnable {
private T obejct;
public UserThread(T obejct) {
this.obejct = obejct;
}
@Override
public void run() {
System.out.println(((User)obejct).getId());
}
public T getObejct() {
return obejct;
}
public void setObejct(T obejct) {
this.obejct = obejct;
}
}
}
上述写法,可用于需要线程返回值的,或者明确需要线程执行完成,且可以保证不占用过多内存。其中Thread.sleep()方法,可以自定义时间。
public static void main(String[] args) throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(Resources.getResourceAsStream("mybatis-config.xml"));
System.out.println("sqlSessionFactory:" + sqlSessionFactory);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 正常查询
List userList5 = sqlSession.selectList("selectUser");
// 流式查询
UserResultHandler userResultHandler = new UserResultHandler(100);
sqlSession.select("selectUser", userResultHandler);
userResultHandler.end();
sqlSession.close();
}
在DefaultResultSetHandler类中handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException 方法中出现了调用区别一个303行,一个206行
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List
这个if判断直接导致了最后实际调用callResultHandler时的区别。
private void callResultHandler(ResultHandler> resultHandler, DefaultResultContext
这个可以从两个方向探讨
一个是比较直接的,即什么时候返回值变成了void,见DefaultSqlSession
一个是为什么会这样,因为自定义实现是,没有把结果加入结果集,见DefaultResultSetHandler
1.DefaultSqlSession
// 正常查询
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 流式查询
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 此方法有返回结果,但是为空,不需要返回(见下边方法)
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.DefaultResultSetHandler
@Override
public List handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 初始化返回值
final List multipleResults = new ArrayList();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 返回值会在调用方法时,被赋值,从上述4.1可看出,只有不传值时才会把结果放入multipleResults
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
// 只有一条结果,就直接取出,多个或空,不处理
private List collapseSingleResultList(List multipleResults) {
return multipleResults.size() == 1 ? (List) multipleResults.get(0) : multipleResults;
}