Mybatis流式查询之ResultHanlde问题

 更新时间:2023年08月11日 09:47:56   作者:认真的老去  
这篇文章主要介绍了Mybatis流式查询之ResultHanlde问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

前言

正常访问数据库的查询操作,都是根据查询sql一次性返回查询结果。

但如果遇到目标数据量过大、且需要全量查询、不能分页、或者内存不想被返回的结果占用过多等需求时(例如导出excel),就可能需要流式查询。

1.准备工作

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.实体类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&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai&amp;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) {
        // 这里获取流式查询每次返回的单条结果
        User user = resultContext.getResultObject();
        handle(user);
    }
   // 串行逐条执行handle
    private void handle(User user) {
        System.out.println(user.getId());
    }
}

适用于导出excel等,,上述写法缺点也很明显,单线程,串行,所有效率慢,但是类似导出excel,符合要求,也不太方便使用多线程。

2.2.流式批量多线程处理handle

为了解决效率问题,有时候会对结果有比如发送请求,更行其他内容等需求时

public class UserResultHandler<T> implements ResultHandler<T> {
    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<Future> 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<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;
        }
    }
}

上述写法,可用于需要线程返回值的,或者明确需要线程执行完成,且可以保证不占用过多内存。

其中Thread.sleep()方法,可以自定义时间。

3.测试

3.1.测试案例

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调用

调用图

在这里插入图片描述

3.2.流式查询debug调用

调用图

在这里插入图片描述

4.问题与部分源码解析

4.1加入resulthandle为什么会流式查询?

在DefaultResultSetHandler类中handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException 方法中出现了调用区别一个303行,一个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,没有就用默认的DefaultResultHandler
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          // 指定handle时,把结果集放到multipleResults中
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          // 非指定handle时,multipleResults中不会有结果
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

这个if判断直接导致了最后实际调用callResultHandler时的区别。

  private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    // 加入handle的会调用自己的代码实现,没有就用默认的DefaultResultHandler里的方法
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
  }

4.2.流式查询为什么没有返回值?

这个可以从两个方向探讨

  • 一个是比较直接的,即什么时候返回值变成了void,见DefaultSqlSession
  • 一个是为什么会这样,因为自定义实现是,没有把结果加入结果集,见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);
      // 此方法有返回结果,但是为空,不需要返回(见下边方法)
      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);
      // 返回值会在调用方法时,被赋值,从上述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<Object> collapseSingleResultList(List<Object> multipleResults) {
    return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
  }

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • MyBatis异常java.sql.SQLSyntaxErrorException的问题解决

    MyBatis异常java.sql.SQLSyntaxErrorException的问题解决

    使用mybatis插入数据时出现java.sql.SQLSyntaxErrorException异常,本文就来介绍一下MyBatis异常的问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • Mybatis update数据库死锁之获取数据库连接池等待

    Mybatis update数据库死锁之获取数据库连接池等待

    这篇文章主要介绍了Mybatis update数据库死锁之获取数据库连接池等待的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-07-07
  • java识别一篇文章中某单词出现个数的方法

    java识别一篇文章中某单词出现个数的方法

    这篇文章主要介绍了java识别一篇文章中某单词出现个数的方法,涉及java字符解析操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-10-10
  • Java实现UDP互发消息

    Java实现UDP互发消息

    这篇文章主要为大家详细介绍了Java实现UDP互发消息,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-07-07
  • Java Web使用Html5 FormData实现多文件上传功能

    Java Web使用Html5 FormData实现多文件上传功能

    这篇文章主要介绍了Java Web使用Html5 FormData实现多文件上传功能,需要的朋友可以参考下
    2017-07-07
  • java控制台实现学生信息管理系统(IO版)

    java控制台实现学生信息管理系统(IO版)

    这篇文章主要为大家详细介绍了java控制台实现学生信息管理系统(IO版),文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • 详解Java对象序列化为什么要使用SerialversionUID

    详解Java对象序列化为什么要使用SerialversionUID

    这篇文章主要介绍了详解Java对象序列化为什么要使用SerialversionUID,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • java生成图片验证码返回base64图片信息方式

    java生成图片验证码返回base64图片信息方式

    这篇文章主要介绍了java生成图片验证码返回base64图片信息方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • SpringMVC使用RESTful接口案例详解

    SpringMVC使用RESTful接口案例详解

    RESTful是一种web软件风格,它不是标准也不是协议,它不一定要采用,只是一种风格,它倡导的是一个资源定位(url)及资源操作的风格,这篇文章主要介绍了SpringBoot使用RESTful接口
    2022-11-11
  • Java手写一个日志框架的示例代码

    Java手写一个日志框架的示例代码

    日志框架是一种用于记录和管理应用程序运行时信息的软件组件,它通常提供了一套API让开发人员能够在代码中插入日志语句,下面我们就来学习一下如何手写一个日志框架吧
    2023-12-12

最新评论