从JDBC裸奔到MyBatis封神:SQL全流程手写ORM实战
前言
一条普通的
SELECT * FROM user WHERE id = 1语句,究竟如何跨越应用与数据库,完成一次数据交互?面试常问的 Statement与PreparedStatement区别、SQL执行底层流程、JDBC/DataSource/MyBatis层级关系,本文一次性讲透!
全文从底层原理到源码拆解,最后手把手带你手写注解版简易MyBatis,吃透ORM框架核心本质,告别面试懵逼!
一、深度拆解:一条SQL的完整取经之路
当代码执行 conn.createStatement().executeQuery(sql),看似一行简单代码,底层经历了客户端封装→网络传输→MySQL服务处理→结果回传四大核心阶段。
1.1 客户端:JDBC驱动的核心工作
MySQL JDBC驱动是Java应用与MySQL的专属翻译官,负责打通应用与数据库的通信桥梁,基础JDBC连接代码如下:
// 基础JDBC连接示例
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false";
Connection conn = DriverManager.getConnection(url, "root", "password");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id, name FROM users WHERE age > 18");
驱动底层核心三件事:
-
建立网络连接:通过TCP三次握手,与MySQL服务器建立稳定通信链路
-
协议封装处理:将文本SQL封装为MySQL专属二进制协议包(区别于HTTP文本协议,更轻量、解析更快)
-
参数预编译处理:若使用PreparedStatement,自动完成SQL预编译与参数绑定封装
1.2 网络传输层:SQL数据包的传输逻辑
JDBC驱动封装好的二进制协议包,通过TCP协议传输至MySQL服务,整个通信分为握手、认证、命令执行、结果返回四个阶段。
传输核心参数:数据包最大尺寸由 max_allowed_packet 控制(默认64MB),开启SSL/TLS加密后,会产生5%-10%的性能开销。
┌─────────────────┐ ┌─────────────────┐
│ 应用程序 (JVM) │ │ MySQL 服务器 │
├─────────────────┤ ├─────────────────┤
│ JDBC 驱动 │ │ 连接线程 │
│ ↓ 封装协议包 │ TCP/IP 传输 │ ↓ 解析协议包 │
└────────┬────────┘ ─────────────────► └────────┬────────┘
│ │
│ ◄───────────────── │
│ 返回结果包 │
│ ▼
│ 执行SQL
1.3 服务端连接层:MySQL连接器(门卫)
数据包抵达MySQL后,首先由连接器统一处理,是数据库的第一道关卡:
-
身份校验:验证账号、密码,加载
mysql.user表权限信息 -
线程分配:每个客户端连接对应独立线程(MySQL5.7+支持线程池优化,提升并发能力)
-
连接管理:通过
wait_timeout控制空闲连接超时自动断开,避免连接堆积
1.4 核心核心:MySQL SQL处理流水线
连接校验通过后,SQL进入MySQL核心处理链路,历经5大核心模块,完成从语法解析到数据读取的全流程:
SQL语句
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. SQL接口层 接收SQL、识别COM_QUERY命令、区分文本/二进制协议 │
├─────────────────────────────────────────────────────────────┤
│ 2. 查询解析器 词法分析拆Token + 语法分析生成AST抽象语法树 │
├─────────────────────────────────────────────────────────────┤
│ 3. 查询优化器 逻辑优化+物理优化,生成最优SQL执行计划 │
├─────────────────────────────────────────────────────────────┤
│ 4. 执行器 调用存储引擎API,执行优化后的SQL计划 │
├─────────────────────────────────────────────────────────────┤
│ 5. InnoDB引擎 缓存查询(Buffer Pool) + B+树索引定位 + 磁盘IO │
└─────────────────────────────────────────────────────────────┘
│
▼
返回结果集(ResultSet)
模块核心作用:
-
解析器:拆解SQL关键字、校验语法合法性,生成结构化AST语法树
-
优化器:自动化简条件、优化子查询、估算成本,选择索引/全表扫描等最优方案(可通过
EXPLAIN查看执行计划) -
存储引擎:优先从Buffer Pool内存缓存读取数据,缓存未命中才触发磁盘IO,大幅提升查询性能
1.5 全网通透:SQL完整执行流程图
┌─────────────────────────────────────────────────────────────────────┐
│ 客户端(JVM) │
│ 应用调用JDBC驱动 → 封装MySQL协议包 → TCP Socket发送数据包 │
└─────────────────────────────────────────────────────────────────────┘
│
▼ TCP/IP 网络传输
┌─────────────────────────────────────────────────────────────────────┐
│ MySQL 服务器 │
│ ① 连接器:身份校验、权限验证、分配连接线程 │
│ ② SQL接口层:接收并识别SQL命令类型 │
│ ③ 查询解析器:词法/语法分析、生成AST、语义校验 │
│ ④ 查询优化器:逻辑+物理优化、生成最优执行计划 │
│ ⑤ 执行器:调用存储引擎API,执行SQL逻辑 │
│ ⑥ InnoDB引擎:缓存查询、索引定位、磁盘IO读取数据 │
└─────────────────────────────────────────────────────────────────────┘
│
▼ 返回二进制结果包
┌─────────────────────────────────────────────────────────────────────┐
│ 客户端(JVM) │
│ JDBC驱动解析二进制包 → 封装为ResultSet → 返回业务层 │
└─────────────────────────────────────────────────────────────────────┘
二、硬核对比:拼接SQL vs 预编译SQL(面试必问)
JDBC提供两种SQL执行方式:Statement(字符串拼接)、PreparedStatement(参数化预编译),二者为父子接口关系:PreparedStatement extends Statement,核心差距极大。
2.1 Statement:简单粗暴,但隐患致命
直接字符串拼接SQL,代码简单但存在致命SQL注入漏洞,且无预编译优化:
// ❌ 高危!存在SQL注入风险
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
注入攻击演示:用户输入 admin' OR '1'='1,最终拼接SQL如下,直接绕过登录校验:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'xxx'
Statement核心特点:
-
无预编译机制,每次执行SQL都需要重新编译
-
字符串拼接方式,极易引发SQL注入攻击
-
低频执行的简单SQL,性能小幅占优(无预编译开销)
-
代码耦合度高,可读性、维护性极差
2.2 PreparedStatement:安全+性能双天花板
采用占位符参数化查询,彻底解决注入问题,同时支持SQL预编译缓存,是企业开发标准用法:
// ✅ 安全规范!参数化查询杜绝注入
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "admin"); // 绑定第一个占位符参数
ps.setString(2, "123456"); // 绑定第二个占位符参数
ResultSet rs = ps.executeQuery();
核心优势:
-
防SQL注入:用户输入仅作为纯数据处理,不会篡改SQL逻辑结构
-
性能优异:首次执行预编译并缓存执行计划,重复执行直接复用
-
代码优雅:SQL逻辑与参数彻底分离,易读易维护
2.3 核心区别终极对比表
| 对比维度 | Statement | PreparedStatement |
|---|---|---|
| SQL预编译 | 不支持,每次执行全量编译 | 预编译一次,执行计划永久缓存 |
| 低频执行性能 | 较高(无预编译开销) | 偏低(存在首次预编译成本) |
| 高频执行性能 | 极低(重复编译,资源浪费) | 极高(复用缓存执行计划) |
| SQL注入风险 | ❌ 极易被注入攻击 | ✅ 天然防御SQL注入 |
| 代码结构 | 字符串拼接,混乱耦合 | 参数与SQL分离,清晰解耦 |
| 参数传递方式 | 字符串直接拼接 | ?占位符 + setXxx()赋值 |
2.4 执行流程极简总结
# Statement 执行流程(低效不安全)
拼接SQL字符串 → 网络传输 → 服务器每次重新编译 → 执行查询
# PreparedStatement 执行流程(高效安全)
发送带?占位符SQL → 服务器预编译缓存执行计划 → 绑定参数 → 直接执行
三、架构拆解:JDBC、DataSource、MyBatis铁三角关系
从原生JDBC到连接池,再到MyBatis框架,是性能优化→效率提升的技术迭代全过程,三者层层封装、各司其职。
3.1 JDBC→DataSource:连接管理的革命性升级
原生 DriverManager 每次获取连接,都要经历TCP握手、数据库认证,频繁创建销毁连接,性能极差:
// 低效写法!每次调用都新建连接
Connection conn = DriverManager.getConnection(url, username, password);
DataSource数据源应运而生,核心能力就是连接池复用:预先初始化一批数据库连接,业务按需获取、用完归还,避免频繁创建销毁连接的开销。
MyBatis内置两种数据源实现:
-
UnpooledDataSource:非池化,每次新建连接,适用于测试环境
-
PooledDataSource:池化连接,生产环境核心实现
3.2 MyBatis核心三层架构+类层级全景图
MyBatis核心分为API接口层、数据处理核心层、基础支撑层,所有ORM能力均由底层组件协同完成:
┌─────────────────────────────────────────────────────────────────────┐
│ API 接口层(用户交互) │
│ SqlSession (顶层API) → 提供所有CRUD操作,面向业务开发 │
└───────────────────────────────────┬───────────────────────────────────┘
▼ 委托调用
┌─────────────────────────────────────────────────────────────────────┐
│ 数据处理核心层(执行核心) │
│ 1. Executor执行器:Simple/Reuse/Batch 三种执行模式 │
│ 2. StatementHandler:适配预编译/存储过程SQL处理 │
│ 3. ParameterHandler:SQL参数绑定赋值 │
│ 4. ResultSetHandler:结果集映射为Java对象 │
│ 5. TypeHandler:Java与JDBC数据类型双向映射 │
└───────────────────────────────────┬───────────────────────────────────┘
▼ 依赖支撑
┌─────────────────────────────────────────────────────────────────────┐
│ 基础支撑层 │
│ DataSource数据源 → 统一提供数据库Connection物理连接 │
└─────────────────────────────────────────────────────────────────────┘
3.3 核心组件功能详解
-
SqlSessionFactoryBuilder:解析全局配置,构建Configuration配置中心,生成SqlSession工厂
-
Configuration:MyBatis核心配置容器,存储数据源、SQL映射、类型处理器、别名等所有配置
-
SqlSession:面向开发者的门面API,所有数据库操作的入口,本身不执行逻辑,仅做调度
-
Executor:SQL执行总调度,负责缓存管理、事务控制、SQL执行调度
-
StatementHandler:封装JDBC Statement所有操作,对接底层数据库交互
-
ParameterHandler/ResultSetHandler:分别负责参数绑定、结果集对象映射
-
TypeHandler:解决Java类型与数据库字段类型的映射适配问题
3.4 三者关系一句话总结
DataSource生产连接、Connection承载物理通信、MyBatis封装JDBC复杂操作,层层封装,让开发者脱离原生JDBC的繁琐冗余代码。
四、手写ORM实战:基于注解的简易MyBatis(CodeStats开源)
理论落地实战!基于 CodeStats开源项目,从零实现一套具备MyBatis核心能力的注解式ORM框架,吃透动态代理、SQL解析、结果映射核心原理。
开源项目地址:https://gitee.com/zhouzuoli/code-stats/
4.1 自研ORM整体架构
┌───────────── 应用层(注解定义SQL) ─────────────┐
│ UserMapper接口 → @Select/@Insert 注解声明SQL │
└───────────────────────┬──────────────────────────┘
▼ JDK动态代理拦截
┌───────────── 代理层(方法拦截处理) ─────────────┐
│ MapperProxy 拦截接口方法、提取注解SQL与参数 │
└───────────────────────┬──────────────────────────┘
▼ SQL语法解析
┌───────────── 解析层(SQL预处理) ────────────────┐
│ SqlParser 解析#{参数}占位符,替换为JDBC?占位符 │
└───────────────────────┬──────────────────────────┘
▼ 执行SQL+结果映射
┌───────────── 执行层(JDBC封装) ─────────────────┐
│ JdbcTemplate执行SQL + 反射映射ResultSet为Java对象 │
└──────────────────────────────────────────────────┘
4.2 Step1:自定义数据库操作注解
// 自定义查询注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Select {
String value(); // 存储SQL语句
int timeout() default 10; // 超时时间
}
// 可扩展@Insert、@Update、@Delete、@Param等注解
4.3 Step2:定义Mapper业务接口
// 自定义Mapper标识注解
@Mapper
public interface UserMapper {
// 注解声明SQL,支持动态参数
@Select("SELECT * FROM user WHERE id = #{id}")
User selectUserById(@Param("id") Integer id);
@Select("SELECT name FROM user")
List<String> getAllNames();
}
4.4 Step3:SQL占位符解析器
核心能力:将MyBatis风格的 #{参数名} 解析为JDBC支持的 ? 占位符,同时提取参数顺序:
public class SqlParser {
// 匹配#{参数}正则表达式
private static final Pattern PLACEHOLDER = Pattern.compile("#\\{(\\w+)\\}");
public static ParsedSql parse(String sql) {
List<String> paramNames = new ArrayList<>();
StringBuffer sb = new StringBuffer();
Matcher m = PLACEHOLDER.matcher(sql);
// 替换占位符并提取参数名
while (m.find()) {
paramNames.add(m.group(1));
m.appendReplacement(sb, "?");
}
m.appendTail(sb);
return new ParsedSql(sb.toString(), paramNames);
}
}
4.5 Step4:动态代理核心(MapperProxy)
通过JDK动态代理拦截Mapper接口方法,完成SQL获取、解析、参数绑定全流程:
public class MapperProxy implements InvocationHandler {
private final JdbcTemplate jdbcTemplate;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 获取方法上的@Select注解SQL
Select select = method.getAnnotation(Select.class);
String sql = select.value();
// 2. 解析SQL:#{xxx} 转为 ?
ParsedSql parsed = SqlParser.parse(sql);
// 3. 封装参数映射关系
Map<String, Object> paramMap = buildParamMap(method, args);
Object[] paramValues = new Object[parsed.getParamNames().size()];
for (int i = 0; i < parsed.getParamNames().size(); i++) {
paramValues[i] = paramMap.get(parsed.getParamNames().get(i));
}
// 4. 执行SQL并完成结果映射
return jdbcTemplate.executeQuery(parsed.getSql(), paramValues,
rs -> mapResult(method.getReturnType(), rs));
}
}
4.6 Step5:JDBC执行模板封装
public class JdbcTemplate {
private DataSource dataSource;
// 通用查询执行方法
public <T> T executeQuery(String sql, Object[] params, ResultSetMapper<T> mapper) {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
// 绑定参数
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
// 执行查询并反射映射结果
ResultSet rs = ps.executeQuery();
return mapper.map(rs);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
4.7 自研ORM完整执行链路
业务调用:userMapper.selectUserById(1)
│
▼
动态代理拦截:MapperProxy.invoke()
│
▼
获取注解SQL:SELECT * FROM user WHERE id = #{id}
│
▼
SQL解析转换:SELECT * FROM user WHERE id = ?、提取参数[id]
│
▼
参数绑定:id=1 赋值给PreparedStatement占位符
│
▼
JdbcTemplate从连接池获取Connection,执行SQL
│
▼
ResultSet结果集通过反射映射为User实体对象
│
▼
返回封装后的Java实体数据
4.8 CodeStats开源项目亮点
该项目不止简易MyBatis,是一套自研完整Java Web框架,无第三方框架依赖,纯手写核心源码:
| 核心模块 | 技术亮点 |
|---|---|
| 自研Spring内核 | IoC依赖注入、MVC路由、AOP责任链预留 |
| 嵌入式Tomcat | NIO线程池连接器、Pipeline-Valve责任链架构 |
| 自研ORM框架 | 注解式Mapper、动态代理、自动结果集映射 |
| 自定义连接池 | 阻塞队列+信号量限流、空闲连接维护、性能优化 |
| 代码分析引擎 | 多线程扫描、30+语言识别、多格式报告输出 |
| AI智能集成 | 适配本地Ollama、云端DeepSeek大模型 |
五、核心知识点速查(面试刚需)
| 核心主题 | 关键知识点 |
|---|---|
| SQL执行全流程 | JDBC封装→TCP传输→MySQL连接器→解析器→优化器→执行器→InnoDB缓存/磁盘IO→结果回传 |
| Statement弊端 | 字符串拼接、无预编译、SQL注入风险、重复编译性能差 |
| PreparedStatement优势 | 参数化防注入、预编译缓存、高频执行性能高、代码解耦 |
| DataSource作用 | 实现数据库连接池,复用Connection,解决原生JDBC连接创建销毁开销大的问题 |
| MyBatis核心架构 | API层(SqlSession)→核心执行层(Executor/处理器)→底层支撑层(数据源/类型映射) |
| 自研ORM核心原理 | 自定义注解+JDK动态代理+SQL占位符解析+JDBC封装+反射结果映射 |
写在最后
从JDBC裸写硬编码,到DataSource连接池优化性能,再到MyBatis封装提升开发效率,最后手写ORM吃透底层原理,这是Java数据库操作技术的完整进化链路。
框架只是工具,底层原理才是核心!吃透SQL执行流程、ORM底层逻辑,才能从容应对面试、解决线上复杂问题。
欢迎前往 CodeStats开源仓库 Star收藏、下载源码实操,一起深耕底层技术!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)