前言

一条普通的 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收藏、下载源码实操,一起深耕底层技术!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐