???关注微信公众号:【芋艿的后端小屋】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

  • 1. 概述
  • 2. JDBC Client 实现
  • 3. MyCAT Server 实现
    • 3.1 创建 PreparedStatement
    • 3.2 执行 SQL
  • 4. 彩蛋

1. 概述

相信很多同学在学习 JDBC 时,都碰到 PreparedStatementStatement。究竟该使用哪个呢?最终很可能是懵里懵懂的看了各种总结,使用 PreparedStatement。那么本文,通过 MyCAT 对 PreparedStatement 的实现对大家能够重新理解下。

本文主要分成两部分:

  1. JDBC Client 如何实现 PreparedStatement
  2. MyCAT Server 如何处理 PreparedStatement

? Let's Go。

2. JDBC Client 实现

首先,我们来看一段大家最喜欢复制粘贴之一的代码,JDBC PreparedStatement 查询 MySQL 数据库:

public class PreparedStatementDemo {public static void main(String[] args) throws ClassNotFoundException, SQLException {// 1. 获得数据库连接Class.forName("com.mysql.jdbc.Driver");Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:8066/dbtest?useServerPrepStmts=true", "root", "123456");// PreparedStatementPreparedStatement ps = conn.prepareStatement("SELECT id, username, password FROM t_user WHERE id = ?");ps.setLong(1, Math.abs(new Random().nextLong()));// executeps.executeQuery();}}复制代码

获取 MySQL 连接时,useServerPrepStmts=true非常非常非常重要的参数。如果不配置,PreparedStatement 实际是个PreparedStatement(新版本默认为 FALSE,据说部分老版本默认为 TRUE),未开启服务端级别的 SQL 预编译。

WHY ?来看下 JDBC 里面是怎么实现的。

// com.mysql.jdbc.ConnectionImpl.java
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {synchronized (getConnectionMutex()) {checkClosed();PreparedStatement pStmt = null;boolean canServerPrepare = true;String nativeSql = getProcessEscapeCodesForPrepStmts() ? nativeSQL(sql) : sql;if (this.useServerPreparedStmts && getEmulateUnsupportedPstmts()) {canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);}if (this.useServerPreparedStmts && canServerPrepare) {if (this.getCachePreparedStatements()) { // 从缓存中获取 pStmtsynchronized (this.serverSideStatementCache) {pStmt = (com.mysql.jdbc.ServerPreparedStatement) this.serverSideStatementCache.remove(makePreparedStatementCacheKey(this.database, sql));if (pStmt != null) {((com.mysql.jdbc.ServerPreparedStatement) pStmt).setClosed(false);pStmt.clearParameters(); // 清理上次留下的参数}if (pStmt == null) {// .... 省略代码 :向 Server 提交 SQL 预编译。}}} else {try {// 向 Server 提交 SQL 预编译。pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);pStmt.setResultSetType(resultSetType);pStmt.setResultSetConcurrency(resultSetConcurrency);} catch (SQLException sqlEx) {// Punt, if necessaryif (getEmulateUnsupportedPstmts()) {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);} else {throw sqlEx;}}}} else {pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);}return pStmt;}
}复制代码
  • 【前者】当 Client 开启 useServerPreparedStmts 并且 Server 支持 ServerPrepareClient 会向 Server 提交 SQL 预编译请求
if (this.useServerPreparedStmts && canServerPrepare) {pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);
}复制代码
  • 【后者】当 Client 未开启 useServerPreparedStmts 或者 Server 不支持 ServerPrepare,Client 创建 PreparedStatement不会向 Server 提交 SQL 预编译请求
pStmt = (PreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);复制代码

即使这样,究竟为什么性能会更好呢?

  • 【前者】返回的 PreparedStatement 对象类是 JDBC42ServerPreparedStatement.java,后续每次执行 SQL 只需将对应占位符?对应的值提交给 Server即可,减少网络传输和 SQL 解析开销。
  • 【后者】返回的 PreparedStatement 对象类是 JDBC42PreparedStatement.java,后续每次执行 SQL 需要将完整的 SQL 提交给 Server,增加了网络传输和 SQL 解析开销。

?:【前者】性能一定比【后者】好吗?相信你已经有了正确的答案。

3. MyCAT Server 实现

3.1 创建 PreparedStatement

该操作对应 Client conn.prepareStatement(....)

MyCAT 接收到请求后,创建 PreparedStatement,并返回 statementId 等信息。Client 发起 SQL 执行时,需要将 statementId 带给 MyCAT。核心代码如下:

// ServerPrepareHandler.java
@Override
public void prepare(String sql) {
LOGGER.debug("use server prepare, sql: " + sql);PreparedStatement pstmt = pstmtForSql.get(sql);if (pstmt == null) { // 缓存中获取// 解析获取字段个数和参数个数int columnCount = getColumnCount(sql);int paramCount = getParamCount(sql);pstmt = new PreparedStatement(++pstmtId, sql, columnCount, paramCount);pstmtForSql.put(pstmt.getStatement(), pstmt);pstmtForId.put(pstmt.getId(), pstmt);}PreparedStmtResponse.response(pstmt, source);
}
// PreparedStmtResponse.java
public static void response(PreparedStatement pstmt, FrontendConnection c) {byte packetId = 0;// write preparedOk packetPreparedOkPacket preparedOk = new PreparedOkPacket();preparedOk.packetId = ++packetId;preparedOk.statementId = pstmt.getId();preparedOk.columnsNumber = pstmt.getColumnsNumber();preparedOk.parametersNumber = pstmt.getParametersNumber();ByteBuffer buffer = preparedOk.write(c.allocate(), c,true);// write parameter field packetint parametersNumber = preparedOk.parametersNumber;if (parametersNumber > 0) {for (int i = 0; i < parametersNumber; i++) {FieldPacket field = new FieldPacket();field.packetId = ++packetId;buffer = field.write(buffer, c,true);}EOFPacket eof = new EOFPacket();eof.packetId = ++packetId;buffer = eof.write(buffer, c,true);}// write column field packetint columnsNumber = preparedOk.columnsNumber;if (columnsNumber > 0) {for (int i = 0; i < columnsNumber; i++) {FieldPacket field = new FieldPacket();field.packetId = ++packetId;buffer = field.write(buffer, c,true);}EOFPacket eof = new EOFPacket();eof.packetId = ++packetId;buffer = eof.write(buffer, c,true);}// send bufferc.write(buffer);
}复制代码

每个连接之间,PreparedStatement 不共享,即不同连接,即使 SQL相同,对应的 PreparedStatement 不同。

3.2 执行 SQL

该操作对应 Client conn.execute(....)

MyCAT 接收到请求后,将 PreparedStatement 使用请求的参数格式化成可执行的 SQL 进行执行。伪代码如下:

String sql = pstmt.sql.format(request.params);
execute(sql);复制代码

核心代码如下:

// ServerPrepareHandler.java
@Override
public void execute(byte[] data) {long pstmtId = ByteUtil.readUB4(data, 5);PreparedStatement pstmt = null;if ((pstmt = pstmtForId.get(pstmtId)) == null) {source.writeErrMessage(ErrorCode.ER_ERROR_WHEN_EXECUTING_COMMAND, "Unknown pstmtId when executing.");} else {// 参数读取ExecutePacket packet = new ExecutePacket(pstmt);try {packet.read(data, source.getCharset());} catch (UnsupportedEncodingException e) {source.writeErrMessage(ErrorCode.ER_ERROR_WHEN_EXECUTING_COMMAND, e.getMessage());return;}BindValue[] bindValues = packet.values;// 还原sql中的动态参数为实际参数值String sql = prepareStmtBindValue(pstmt, bindValues);// 执行sqlsource.getSession2().setPrepared(true);source.query(sql);}
}private String prepareStmtBindValue(PreparedStatement pstmt, BindValue[] bindValues) {String sql = pstmt.getStatement();int[] paramTypes = pstmt.getParametersType();StringBuilder sb = new StringBuilder();int idx = 0;for (int i = 0, len = sql.length(); i < len; i++) {char c = sql.charAt(i);if (c != '?') {sb.append(c);continue;}// 处理占位符?int paramType = paramTypes[idx];BindValue bindValue = bindValues[idx];idx++;// 处理字段为空的情况if (bindValue.isNull) {sb.append("NULL");continue;}// 非空情况, 根据字段类型获取值switch (paramType & 0xff) {case Fields.FIELD_TYPE_TINY:sb.append(String.valueOf(bindValue.byteBinding));break;case Fields.FIELD_TYPE_SHORT:sb.append(String.valueOf(bindValue.shortBinding));break;case Fields.FIELD_TYPE_LONG:sb.append(String.valueOf(bindValue.intBinding));break;// .... 省略非核心代码}}return sb.toString();
}复制代码

4. 彩蛋

? 看到此处是不是真爱?!反正我信了。
给老铁们额外加个?。

细心的同学们可能已经注意到 JDBC Client 是支持缓存 PreparedStatement,无需每次都让 Server 进行创建。

当配置 MySQL 数据连接 cachePrepStmts=true 时开启 Client 级别的缓存。But,此处的缓存又和一般的缓存不一样,是使用 remove 的方式获得的,并且创建好 PreparedStatement 时也不添加到缓存。那什么时候添加缓存呢?在 pstmt.close() 时,并且pstmt 是通过缓存获取时,添加到缓存。核心代码如下:

// ServerPreparedStatement.java
public void close() throws SQLException {MySQLConnection locallyScopedConn = this.connection;if (locallyScopedConn == null) {return; // already closed}synchronized (locallyScopedConn.getConnectionMutex()) {if (this.isCached && isPoolable() && !this.isClosed) {clearParameters();this.isClosed = true;this.connection.recachePreparedStatement(this);return;}realClose(true, true);}
}
// ConnectionImpl.java
public void recachePreparedStatement(ServerPreparedStatement pstmt) throws SQLException {synchronized (getConnectionMutex()) {if (getCachePreparedStatements() && pstmt.isPoolable()) {synchronized (this.serverSideStatementCache) {this.serverSideStatementCache.put(makePreparedStatementCacheKey(pstmt.currentCatalog, pstmt.originalSql), pstmt);}}}
}复制代码

为什么要这么实现?PreparedStatement 是有状态的变量,我们会去 setXXX(pos, value),一旦多线程共享,会导致错乱。

? 这个“彩蛋”还满意么?请关注我的公众号:芋艿的后端小屋。下一篇更新:《MyCAT源码解析 —— MongoDB》,极大可能就在本周噢。

wechat_mp

另外推荐一篇文章:《JDBC PreparedStatement》。

JDBC PreparedStatement 实现原理【推荐阅读】相关推荐

  1. Java程序员到架构师的推荐阅读书籍

    作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从.我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序,推荐给大家,特别是那些想不断提高自己技术水 ...

  2. MySQL JDBC PreparedStatement

    JDBC作为JAVA访问数据库的一套规范与标准,统一了数据库操作的API,大大简化了程序开发工作.不过由于历史原因,MySQL对JDBC默认的实现与规范定义或者说其它数据库如Oracle并不一致,为了 ...

  3. Java程序员推荐阅读书籍

    作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从.我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序,推荐给大家,特别是那些想不断提高自己技术水 ...

  4. 云计算:OpenStack、Docker、K8S(Kubernetes容器编排工具)的演进史 | 附推荐阅读

    目录 引子 OpenStack 的诞生 OpenStack 是什么 Docker 的出现 K8S(Kubernetes) - 为 Docker 而生 推荐阅读 引子 作为一名程序员,设计程序架构.优化 ...

  5. typescript - 一种思维方式(推荐阅读)

    转载:https://segmentfault.com/a/1190000018953855 电影<降临>中有一个观点,语言会影响人的思维方式,对于前端工程师来说,使用 typescrip ...

  6. 2012年每周推荐阅读汇总

    2012年下半年,我开始了一项名为"每周推荐阅读"的计划,也就是每周给部门里的同事推荐些文章,拓展一下大家的阅读面,不知不觉已经年底了,因为每次的推荐阅读都不太有反馈,所以本打算在 ...

  7. JDBC简介及原理和使用介绍

    JDBC简介及原理和使用介绍 JDBC简介 jdbc概述 ​ Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据 ...

  8. 推荐阅读10本书:提升个人管理能力和领导力

    阅读是一种习惯,阅读更是积累知识,提升自我能力的一种有效的手段.很难想象一个人整天把"我是热爱学习的人"挂在嘴边,但是甚至半年都不会阅读一本书,相信这样的人只是说说而已. 最近在面 ...

  9. 阿里年薪70w+级程序员,强烈推荐阅读

    阿里年薪70w+级程序员,强烈推荐阅读 之前在钉钉"Spring Cloud Alibaba 开源讨论群"官方群里认识了一位大佬,群里讨论的时候发现这位大佬和我住在一个地方的不同小 ...

最新文章

  1. C#调用TSC条码打印机打印二维码(转)
  2. 【python】点分十进制ip与数字互转
  3. G4Sui老师的pair project(197)
  4. 【神经网络与深度学习】读书笔记
  5. 关于Discuz X2 论坛备份详解(论坛搬家)
  6. python常用指令速查
  7. 趣学python编程第六章答案_Python核心编程-第六章-习题
  8. Java学习笔记(五)——数组
  9. [Ubuntu] 二、安卓模拟器
  10. android按钮添加音效,Android中为按钮设置点击音效
  11. python 图像对比度_Python: PS 图像调整--对比度调整
  12. JS制作一个简单的网页倒计时器
  13. 中国电影的网络付费点播发行:现状与展望
  14. 2023年最新微信小程序获取用户openid、头像昵称的填写能力和方法原生写法
  15. Mysql横向分组统计
  16. HyperLynx(十)BoardSim和PCB板级仿真分析(一)
  17. 第2次作业:软件案例分析
  18. linux怎么滑动命令行窗口_如何在Linux命令行界面愉快进行性能测试
  19. VC.PE.天使等解释
  20. 构建计算机网络的难点,2017中国石油大学继续教育计算机网络基础答案难点.docx...

热门文章

  1. at命令不生效 linux_帮你精通Linux:简约却不简单的ls命令
  2. 不用精子就能繁育后代,科学家只用1个卵细胞就培育出健康小鼠,来自上交医学院 | PNAS...
  3. 加州理工让无人机长出腿:走路飞行无缝切换,还能玩滑板、走钢丝|Sicence子刊...
  4. 北大电池新研究登上Nature:3万次循环测试,性能衰减不到30%,大幅提高锂空电池性能...
  5. GitHub趋势榜第一:用小姐姐自拍,生成二次元萌妹子,神情高度还原,反过来也可以...
  6. mysql中获取一天、一周、一月时间数据的各种sql语句写法
  7. 《乐高EV3机器人搭建与编程》——2.2 颜色设计
  8. [TypeScript] Using Interfaces to Describe Types in TypeScript
  9. MyBatis学习--简单的增删改查
  10. JQuery+ajax+jsonp 跨域访问