主题
手写持久层框架(MyBatis 原理铺垫)
从裸 JDBC 出发,一步步封装出一个 mini-MyBatis,用以理解 MyBatis 核心组件的设计动机。参考实现:ipersistence。
一、JDBC 的 4 个问题与对策
| 问题 | 原生 JDBC | 解决 |
|---|---|---|
| 连接频繁创建/释放 | DriverManager.getConnection | 连接池(C3P0 / Druid) |
| SQL 硬编码 | Java 里写死 SQL | SQL 抽到配置文件(如 mapper.xml) |
| 参数/占位符一一对应难维护 | 手工 setXxx | 配置文件 + 解析 #{} 自动设参 |
| 结果集解析硬编码 | 手工 resultSet.getInt("id") | 反射/内省按字段自动封装成实体 |
二、整体设计
使用端只需提供两个配置:
sqlMapConfig.xml:数据库连接信息(driver/url/user/password)+ Mapper 资源路径mapper.xml:SQL 语句、参数类型、返回类型(key =namespace.id)
框架侧对 JDBC 做封装,核心类如下:
Resources // 根据路径把配置文件读成 InputStream
Configuration // 核心配置容器:DataSource + Map<statementId, MappedStatement>
MappedStatement // 单条 SQL 的映射:id / resultType / parameterType / sql / sqlCommandType
SqlSessionFactoryBuilder // build(InputStream) → 解析配置 → 产出 SqlSessionFactory
SqlSessionFactory // openSession() → SqlSession
SqlSession // selectList/selectOne/insert/update/delete + getMapper(生成 DAO 代理)
Executor // 真正执行 JDBC 的类:query/insert/update/delete
BoundSql // 解析后的 SQL(#{...} → ?)+ 参数名列表解析链路:SqlSessionFactoryBuilder.build → XMLConfigBuilder.parseConfig(dom4j 解析 sqlMapConfig.xml,建 C3P0 数据源,并逐个解析 mapper.xml 生成 MappedStatement 存入 Configuration)→ new SqlSessionFactoryImpl(configuration)。
三、Executor 的关键细节
#{}解析:GenericTokenParser+ParameterMappingTokenHandler把#{xxx}替换成?,并收集参数名。- 设参
setObject:多参数直接按序 set;单实体参数则反射读取字段值 set。 - 结果封装:用
ResultSetMetaData取列名,借助PropertyDescriptor(内省)按列名调用实体的 writeMethod 封装。
四、getMapper:JDK 动态代理
SqlSession.getMapper(Class) 用 Proxy.newProxyInstance 为 DAO 接口生成代理:
statementId = 接口全路径 + "." + 方法名(所以mapper.xml的 namespace 必须是接口全路径、id 必须与方法名一致);- 根据
MappedStatement.sqlCommandType分派到selectList/selectOne/insert/update/delete; - 返回值类型若是参数化
List(泛型),走selectList,否则selectOne。
常见误解
- "MyBatis 很神秘" —— 本质就是上面这套:配置解析 + 代理 + Executor 封装 JDBC。
- "namespace/id 随便写" —— 错,必须和接口/方法名对齐,代理才能拼出正确
statementId。
关联
- 上游:自定义持久层框架
- 同域:MyBatis架构与插件机制(四大组件 ≈ 这里的 Executor/StatementHandler 等抽象)· MyBatis缓存机制
- 域地图:Java后端