MySQL 普通查询、流式查询、游标查询以及使用 mybatis 如何实现

2023-05-16

MySQL 普通查询、流式查询、游标查询以及使用 mybatis 如何实现

  • MySQL 普通查询、流式查询、游标查询以及使用 mybatis 如何实现
    • 普通查询
    • 流式查询
    • 游标查询
    • mybatis 如何实现
    • 参考

MySQL 普通查询、流式查询、游标查询以及使用 mybatis 如何实现

JDBC 查询的大体流程是:JDBC 向 Mysql 数据库请求数据,数据库查询后通过网络将数据发送给我们本机,数据先存储在 socket buffer 中,然后才会将数据读入到 JVM 中(存在堆中)。也就是说数据存储在 socket buffer 中的时候是不消耗堆的空间的。

假设我们要查询 10 条数据:select id, name from user limit 10,各种查询的处理过程如下:

普通查询:就是我们常用的查询,Mysql 会将 10 条数据全部发送到本机,将 10 条数据先存储在 socket buffer 中,然后将 10 条数据全部读到堆中。

流式查询:Mysql 会将数据源源不断的发送到本机,存储在 socket buffer 中,每次调用ResultSet.next()方法时只会读取 1 条数据到堆中(不需要数据全部发送完毕就能读取)。

游标查询:Mysql 每次返回的数据条数取决于fetchSize 参数,假设 fetchSize = 3,那么 Mysql 每次只会返回 3 条数据,存储在 socket buffer 中,然后将 3 条数据都读入到堆中,调用ResultSet.next() 时堆有数据就直接返回,没数据会再向 Mysql 请求 3 条数据,这样循环往复(3、3、3、1),直到 10 条数据都读完。

普通查询

public static void main(String[] args) throws SQLException {
    String sql = "select id, name from user limit 10";
    Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
    Statement st = conn.createStatement();
    ResultSet rs = st.executeQuery(sql);

    while (rs.next()) {
        System.out.println(rs.getInt("id") + " " + rs.getString("name"));
    }
}

rs.next() 打断点可以进入 com.mysql.cj.jdbc.result.ResultSetImpl.next(),可以看到 rowData 的实现类是 ResultsetRowsStatic,且 szie 是 10,这说明将 10 条记录到加载进了堆中。

image-20230318124807101

ResultsetRowsStatic.next() 方法很简单,先 index++,再 List.get(index)。

image-20230318131256279

image-20230318131145946

流式查询

image-20230318125659044

上图是流式查询的官方文档,大意是要使用流式查询,在创建 Statement 时必须传参 java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY,且必须把 fetchSize 设置为 Integer.MIN_VALUE

有个注意事项:必须读完或关闭 ResultSet,才能在同一个 Connection 上再执行其他操作,否则会抛异常。换句话说就是一个流式查询会独占一个数据库连接,所以使用流式查询时应尽快读取结果集,避免将连接耗尽的情况出现。

public static void main(String[] args) throws SQLException {
    String sql = "select id, name from user limit 10";
    Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
    // 重点 TYPE_FORWARD_ONLY、CONCUR_READ_ONLY、fetchSize = Integer.MIN_VALUE
    Statement st = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
    st.setFetchSize(Integer.MIN_VALUE);

    ResultSet rs = st.executeQuery(sql);
    while (rs.next()) {
        System.out.println(rs.getInt("id") + " " + rs.getString("name"));
    }
}

image-20230318130904491

rowData 的实现类为 ResultsetRowsStreaming,头部的注释与官方文档相同的意思。

image-20230318132401340

image-20230318132321743

read() 最终会走到 ResultsetRowReader.read()

image-20230318133623730

游标查询

image-20230318133125747

要使用游标查询,必须在设置连接属性 useCursorFetch=truefetchSize

public static void main(String[] args) throws SQLException {
    String sql = "select id, name from user limit 10";
    // 连接属性 useCursorFetch=true
    Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useCursorFetch=true", "root", "root");
    Statement st = conn.createStatement();
    // fetchSize
    st.setFetchSize(3);

    ResultSet rs = st.executeQuery(sql);
    while (rs.next()) {
        System.out.println(rs.getInt("id") + " " + rs.getString("name"));
    }
}

image-20230318133856785

rowData 的实现类是 ResultsetRowsCursor

image-20230318135836777

image-20230318135354302

image-20230318140108606

mybatis 如何实现

com.mysql.cj.jdbc.ConnectionImplcreateStatement()、prepareStatement(String sql) 方法的默认值就是使用流式查询所需要的值

private static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;

private static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;

@Override
public java.sql.Statement createStatement() throws SQLException {
    return createStatement(DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY);
}

@Override
public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException {
    return prepareStatement(sql, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY);
}

mybatis 创建 StatementBaseStatementHandlerprepare 方法

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    // 创建 statement
    statement = instantiateStatement(connection);
    setStatementTimeout(statement, transactionTimeout);
    // 设置 fetchSize
    setFetchSize(statement);
    return statement;
  } catch (SQLException e) {
    closeStatement(statement);
    throw e;
  } catch (Exception e) {
    closeStatement(statement);
    throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
  }
}

instantiateStatement 方法有三个实现:

  • CallableStatementHandler:存储过程相关的

  • SimpleStatementHandler:返回 Statement

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.createStatement();
    } else {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
    
  • PreparedStatementHandler:返回 PreparedStatement

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
    

分析之后发现 mybatis 实现流式查询、游标查询很简单,第一需要设置连接属性 useCursorFetch=true

jdbc:mysql://127.0.0.1:3306/test?useCursorFetch=true

第二设置 fetchSize,流式查询设置成 Integer.MIN_VALUE-2147483648;游标查询设置成我们想要的值:

<!-- 流式查询 -->
<select id="streamingQuery" fetchSize="-2147483648" resultMap="BaseResultMap">
  select id, name from user limit 10
</select>

<!-- 游标查询 -->
<select id="cursorQuery" fetchSize="3" resultMap="BaseResultMap">
  select id, name from user limit 10
</select>

第三 Mapper 需要返回 Corsor<User>

Cursor<User> streamingQuery();

Cursor<User> cursorQuery();

第四 for 循环获取数据:

@RestController
public class TestController {

    private final UserMapper userMapper;

    public TestController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    // 注意点:获取数据时要保证数据库连接没有被回收,这里使用了 @Transactional 保证
    @Transactional
    @GetMapping("streaming")
    public List<User> streamingQuery() {
        Cursor<User> cursor = userMapper.streamingQuery();
        return getData(cursor);
    }

    @Transactional
    @GetMapping("cursor")
    public List<User> cursorQuery() {
        Cursor<User> cursor = userMapper.cursorQuery();
        return getData(cursor);
    }

    public List<User> getData(Cursor<User> cursor) {
        List<User> list = new LinkedList<>();
        cursor.forEach(list::add);
        return list;
    }
}

可以在 com.mysql.cj.jdbc.result.ResultSetImpl.next() 打断点验证一下 rowData 的类型。

参考

  • MySQL :: MySQL Connector/J 8.0 Developer Guide :: 6.4 JDBC API Implementation Notes
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL 普通查询、流式查询、游标查询以及使用 mybatis 如何实现 的相关文章

  • Keil AC5/Keil AC6/IAR指定数据绝对存储地址

    文章目录 1 前言2 实现方法3 例子 1 前言 编译过程中 xff0c 指定数据绝对存储地址在实际项目中会经常使用到或者必须用到 xff0c 这样使得项目实现某些功能可以非常灵活 xff0c 常用的场景有 xff1a IAP升级时候 xf
  • 嵌入式开发常用到的在线工具

    文章目录 IP地址计算常用加解密 xff0c AES DSE Base64 MD5异或 xff08 BCC xff09 校验CRC计算十六进制格式化字符串Json格式化HTML运行器常用在线编译器 xff08 C C 43 43 C JAV
  • STM32H7xx 串口DMA发送&接收(LL库)

    文章目录 1 前言2 STM32H7实现2 1 关键步骤2 2 注意事项 3 代码仓库 1 前言 关于串口DMA收发实现 xff0c 不同CPU其套路都是类似的 xff0c 不同之处在于寄存器配置 依赖BSP库等差异 串口DMA收发详细实现
  • 正交编码器溢出处理

    文章目录 1 正交编码器1 1 参数特性1 2 应用范围 2 正交编码器使用2 1 溢出问题2 2 中断模式2 3 循环模式延伸 1 正交编码器 正交编码器一般指的是增量式光栅 xff08 磁栅 xff09 编码器 xff0c 通常有三路输
  • PX4多旋翼期望姿态矩阵生成算法

    1 PX4多旋翼期望姿态生成算法 1 1 求期望体轴X轴向量1 2 求期望体轴Y轴向量1 3 求期望姿态矩阵1 4 求期望姿态角 1 PX4多旋翼期望姿态生成算法 PX4多旋翼期望姿态生成采用旋转矩阵方法 xff0c 基本思路为根据外环解算
  • git安装

    Git介绍 分布式 xff1a Git版本控制系统是一个分布式的系统 xff0c 是用来保存工程源代码历史状态的命令行工具 保存点 xff1a Git的保存点可以追踪源码中的文件 并能得到某一个时间点上的整个工程项目的状态 xff1b 可以
  • linux更改ssh连接方式将publickey改为用户名密码登录

    1 vim etc ssh sshd config 2 PermitRootLogin no 改为 PermitRootLogin yes 3 service restart sshd
  • dsp2812 pmsm foc之速度环电流环

    61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 速度环PI 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • leetcode(c++)

    放假没事刷几道leetcode xff0c 一些常见典型题的答案和解析 平时python用的比较多 xff0c 但分析复杂度的时候用python编程不方便 xff0c 所以刷题的时候用了c 43 43 C 43 43 基础 C 43 43
  • 基于STM32的超声波雷达项目【可拟合构建平面地图】(代码开源)

    前言 xff1a 本文为手把手教学基于STM32的超声波雷达 项目 HC SR04雷达 本次项目采用的是STM32作为MCU xff0c 搭配常用的HC SR04超声波模块与舵机SG90实现模拟雷达检测 的效果 模拟了雷达图UI 可以拟合构
  • android sdk manager不显示更新,只显示已安装,解决办法

    启动 Android SDK Manager xff0c 打开主界面 xff0c 依次选择 Tools Options xff0c 弹出 Android SDK Manager Settings 窗口 xff1b 在 Android SDK
  • detectron2学习:KeyError: “No object named ‘XXXXX‘ found in ‘BACKBONE‘ registry!“

    问题来源 在使用FB的框架detectron2改写模型的时候碰到了KeyError 34 No object named 39 XXXXX 39 found in 39 BACKBONE 39 registry 34 的bug 分析 xff
  • linux内核网络协议栈--linux网络设备理解(十三)

    网络层次 linux网络设备驱动与字符设备和块设备有很大的不同 字符设备和块设备对应 dev下的一个设备文件 而网络设备不存在这样的设备文件 网络设备使用套接字socket访问 xff0c 虽然也使用read write系统调用 xff0c
  • makefile使用--命令(三)

    一 Make的概念 Make这个词 xff0c 英语的意思是 34 制作 34 Make命令直接用了这个意思 xff0c 就是要做出某个文件 比如 xff0c 要做出文件a txt xff0c 就可以执行下面的命令 span class t
  • 【笔记】ubuntu18.04 ros melodic turtlebot3 源码下,导航gmapping仿真

    编写本笔记原因 xff1a 源码编译没问题 xff0c 但是在运行roslaunch turtlebot3 slam turtlebot3 slam launch slam methods 61 cartgrapher时出现下面这个错误 x
  • 从kernel层面分析synchronized、volatile,进大厂必备硬核小伎俩(上)

    synchronized volatile对于java程序员来说再熟悉不过了 xff0c 但 是你知道这两个关键字底层是如何实现的吗 xff08 甚至在操作系层面是 通过什么指令来实现的 xff09 xff1f 以及与其相关的术语 xff1
  • linux内核原理剖析——内存寻址(一)

    最近总想分享点硬核的原创文章出来 xff0c 一是硬核技术是一个程序员真正应该修炼 的内功 xff1b 二是修炼硬核技能是通往架构师领域的必经之路 本系列文章将分享关 于linux内核设计原理相关的内容 xff0c 希望能打通我们的七经八脉
  • linux内核原理剖析——磁盘寻址、分区

    继上一篇 lt lt linux内核原理剖析 内存寻址 xff08 一 xff09 gt gt 之后 xff0c 发现大家 对底层技术关注度比较高之后 xff0c 今天继上一篇的内存寻址一文后 xff0c 补 充一篇关于更为底层的 磁盘寻址
  • Redis集群从搭建到设计,总有一些你不曾了解的东西

    Redis集群是Redis服务器高可用的设计模型 xff0c 也是我们线上应用最多的Redis部署架构 本文主要针对Redis集 群入门搭建 Redis集群节点及其底层数据结构 hash槽 重新分片 消息等核心操作及原理进行分享 本文是基于
  • java成神之路学习线路

    自己总结了下java后端学习线路 xff0c 也是我八年的工作学习积累 xff0c 供各位同学参考 线路图还不全 xff0c 之后我会逐渐补全 下面思维导图中的技术 xff0c 我争取在2020年的博文中都分享给大家 xff0c 形成一个系

随机推荐