Skip to content

手写持久层框架(MyBatis 原理铺垫)

从裸 JDBC 出发,一步步封装出一个 mini-MyBatis,用以理解 MyBatis 核心组件的设计动机。参考实现:ipersistence

一、JDBC 的 4 个问题与对策

问题原生 JDBC解决
连接频繁创建/释放DriverManager.getConnection连接池(C3P0 / Druid)
SQL 硬编码Java 里写死 SQLSQL 抽到配置文件(如 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.buildXMLConfigBuilder.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

关联

最近更新