Mybatis流式查詢之ResultHanlde問(wèn)題
前言
正常訪問(wèn)數(shù)據(jù)庫(kù)的查詢操作,都是根據(jù)查詢sql一次性返回查詢結(jié)果。
但如果遇到目標(biāo)數(shù)據(jù)量過(guò)大、且需要全量查詢、不能分頁(yè)、或者內(nèi)存不想被返回的結(jié)果占用過(guò)多等需求時(shí)(例如導(dǎo)出excel),就可能需要流式查詢。
1.準(zhǔn)備工作
1.1.Mybaits的jar包引入
注:idea必須配置build,否則掃描不到src下的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>relife</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>relife-object</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>1.2.實(shí)體類User
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;
}
}1.3.mybatis-config.xml和userMapper.xml
mybatis-config.xml(resource根目錄)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/relife/mybatis/userMapper.xml"/>
</mappers>
</configuration>userMapper.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.relife.object.User">
<select id="selectUser" resultType="com.relife.object.User">
select id,name,age from user
</select>
</mapper>2.流式處理
2.1流式逐條處理handle
public class UserResultHandler implements ResultHandler<User> {
@Override
public void handleResult(ResultContext<? extends User> resultContext) {
// 這里獲取流式查詢每次返回的單條結(jié)果
User user = resultContext.getResultObject();
handle(user);
}
// 串行逐條執(zhí)行handle
private void handle(User user) {
System.out.println(user.getId());
}
}適用于導(dǎo)出excel等,,上述寫法缺點(diǎn)也很明顯,單線程,串行,所有效率慢,但是類似導(dǎo)出excel,符合要求,也不太方便使用多線程。
2.2.流式批量多線程處理handle
為了解決效率問(wèn)題,有時(shí)候會(huì)對(duì)結(jié)果有比如發(fā)送請(qǐng)求,更行其他內(nèi)容等需求時(shí)
public class UserResultHandler<T> implements ResultHandler<T> {
public final Logger logger = Logger.getLogger(this.getClass());
/**
* 線程池線程數(shù)
*/
private int threadPollNum = 100;
public UserResultHandler() {
}
public UserResultHandler(int threadPollNum) {
this.threadPollNum = threadPollNum;
}
// 線程池
public ExecutorService executorService = Executors.newFixedThreadPool(threadPollNum);
// 線程執(zhí)行結(jié)果
public List<Future> futureList = new ArrayList<>();
@Override
public void handleResult(ResultContext<? extends T> resultContext) {
// 這里獲取流式查詢每次返回的單條結(jié)果
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("條數(shù):" + resultContext.getResultCount() + "->未完成結(jié)果" + futureList.size());
}
UserThread ut = new UserThread(user);
Future<?> future = executorService.submit(ut);
futureList.add(future);
}
/**
* 保證所有線程執(zhí)行完成,并關(guān)閉線程池
*/
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<T> 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;
}
}
}上述寫法,可用于需要線程返回值的,或者明確需要線程執(zhí)行完成,且可以保證不占用過(guò)多內(nèi)存。
其中Thread.sleep()方法,可以自定義時(shí)間。
3.測(cè)試
3.1.測(cè)試案例
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<User> userList5 = sqlSession.selectList("selectUser");
// 流式查詢
UserResultHandler userResultHandler = new UserResultHandler(100);
sqlSession.select("selectUser", userResultHandler);
userResultHandler.end();
sqlSession.close();
}3.1.正常查詢debug調(diào)用
調(diào)用圖

3.2.流式查詢debug調(diào)用
調(diào)用圖

4.問(wèn)題與部分源碼解析
4.1加入resulthandle為什么會(huì)流式查詢?
在DefaultResultSetHandler類中handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException 方法中出現(xiàn)了調(diào)用區(qū)別一個(gè)303行,一個(gè)206行
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 有的話就使用傳入的ResultHandle,沒(méi)有就用默認(rèn)的DefaultResultHandler
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 指定handle時(shí),把結(jié)果集放到multipleResults中
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 非指定handle時(shí),multipleResults中不會(huì)有結(jié)果
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}這個(gè)if判斷直接導(dǎo)致了最后實(shí)際調(diào)用callResultHandler時(shí)的區(qū)別。
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
// 加入handle的會(huì)調(diào)用自己的代碼實(shí)現(xiàn),沒(méi)有就用默認(rèn)的DefaultResultHandler里的方法
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}4.2.流式查詢?yōu)槭裁礇](méi)有返回值?
這個(gè)可以從兩個(gè)方向探討
- 一個(gè)是比較直接的,即什么時(shí)候返回值變成了void,見DefaultSqlSession
- 一個(gè)是為什么會(huì)這樣,因?yàn)樽远x實(shí)現(xiàn)是,沒(méi)有把結(jié)果加入結(jié)果集,見DefaultResultSetHandler
1.DefaultSqlSession
// 正常查詢
@Override
public <E> List<E> 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);
// 此方法有返回結(jié)果,但是為空,不需要返回(見下邊方法)
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<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 初始化返回值
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 返回值會(huì)在調(diào)用方法時(shí),被賦值,從上述4.1可看出,只有不傳值時(shí)才會(huì)把結(jié)果放入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);
}
// 只有一條結(jié)果,就直接取出,多個(gè)或空,不處理
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot詳細(xì)列舉常用注解的說(shuō)明
在開發(fā)SpringBoot程序的過(guò)程中,有可能與其他業(yè)務(wù)系統(tǒng)進(jìn)行對(duì)接開發(fā),獲取封裝公共的API接口等等,下面這篇文章主要給大家介紹了關(guān)于SpringBoot常見的注解的相關(guān)資料,需要的朋友可以參考下2022-06-06
Mybatis 中的一對(duì)一,一對(duì)多,多對(duì)多的配置原則示例代碼
這篇文章主要介紹了 Mybatis 中的一對(duì)一,一對(duì)多,多對(duì)多的配置原則示例代碼,需要的朋友可以參考下2017-03-03
Java的this關(guān)鍵字的使用與方法的重載相關(guān)知識(shí)
這篇文章主要介紹了Java的this關(guān)鍵字的使用與方法的重載相關(guān)知識(shí),是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09
第一次使用Android Studio時(shí)你應(yīng)該知道的一切配置(推薦)
這篇文章主要介紹了第一次使用Android Studio時(shí)你應(yīng)該知道的一切配置(推薦) ,需要的朋友可以參考下2017-09-09
java實(shí)現(xiàn)List中對(duì)象排序的方法
這篇文章主要介紹了java實(shí)現(xiàn)List中對(duì)象排序的方法,涉及Java中的遍歷與對(duì)象操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11

