MyBatis框架学习笔记
手写框架篇
手写MyBatis框架分析
- 回顾JDBC
读取配置文件
有两类配置文件需要加载:全局配置文件和映射文件 XML文件
全局配置文件加载
XMLConfigBuilder:专门用来解析全局配置文件的
- 根据配置文件路径,读取配置文件(InputStream流)
- 创建 Document 对象(没有对 MyBatis 语义进行解析)
- 根据 MyBatis 语义去解析 Document 对象,将解析结果封装到一个对象(Configuration)
- 解析数据源信息(dom4j + path)
- 将数据源信息封装到 Configuration 对象中
- 解析映射文件信息
- 解析数据源信息(dom4j + path)
映射文件加载
XMLMapperBuilder:专门用来解析映射文件的
- 根据映射文件路径,读取映射文件(InputStream流)
- 创建 Document 对象(没有对 MyBatis 语义进行解析)
- 根据 MyBatis 语义去解析 Document 对象,将解析结果封装到一个对象集合(Map<String, MappedStatement>)
- 先将未解析的SQL语句封装到一个对象中(SqlSource)
执行JDBC
问题:SqlSession如何得到 Configuration ?
jdbc程序太重,需要设计更简洁的面向用户的接口(SqlSession)
- SqlSession接口定义(selectOne | selectList | insert | update | delete)
- SqlSession实现jdbc流程如下:
准备SQL语句
- 从 Configuration 对象中根据 statementId 获取 Map<String, MappedStatement>中的指定MappedStatement对象(??)
- 调用 SqlSource 的 getBoundSql 方法,获取 BoundSql (解析之后的SQL语句,入参信息集合【入参名称、入参类型】,入参对象)
- 从 MappedStatement 对象中获取 SQL 语句
创建Connection
- 从 Configuration 对象中获取 DataSource,通过 DataSource 创建 Connection
执行SqlSession
- 从 Configuration 对象中根据 statementId 获取 Map<String, MappedStatement>中的指定MappedStatement 对象
- 从 MappedStatement 对象中获取 statementType 信息,目的是创建对应的 Statement 对象
- 设置参数(??)
- 执行statement对象
- 处理 ResultSet(??)
手写MyBatis框架
回顾JDBC
MyBatis框架V1版本
- properties文件
# 数据库连接配置
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://192.168.254.128:3306/ssm?characterEncoding=utf-8
db.username=root
db.password=123456
# 根据用户id查询用户信息
db.sql.selectUserById=select * from user where id = ?
db.sql.selectUserById.resultType=com.yw.mybatis.example.po.User
# 根据用户名称查询用户信息
db.sql.selectUserByUsername=select * from user where username = ?
db.sql.selectUserByUsername.resultType=com.yw.mybatis.example.po.User
# 根据用户名称和性别查询用户信息
db.sql.selectUserByCriteria=select * from user where username = ? and sex = ?
db.sql.selectUserByCriteria.columnNames=username,sex
db.sql.selectUserByCriteria.resultType=com.yw.mybatis.example.po.User
- 代码实现:
/**
* 解决硬编码问题:properties文件
*/
public class MyBatisV1 {
private Properties properties = new Properties();
@Test
public void test() {
// 加载配置文件
loadProperties("mybatis.properties");
// 测试
System.out.println("selectUserById");
System.out.println(selectList("selectUserById", 1));
System.out.println("selectUserByUsername");
System.out.println(selectList("selectUserByUsername", "李四"));
System.out.println("selectUserByCriteria");
Map<String, Object> param = new HashMap<>(2);
param.put("username", "张三");
param.put("sex", "男");
System.out.println(selectList("selectUserByCriteria", param));
}
private void loadProperties(String location) {
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(location)) {
properties.load(is);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
private <T> List<T> selectList(String id, Object param) {
List<T> results = new ArrayList<>();
try {
// // 1、加载驱动
// Class.forName(JDBC_DRIVER);
// 解决连接获取时的硬编码问题和频繁连接的问题
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(properties.getProperty("db.driver"));
dataSource.setUrl(properties.getProperty("db.url"));
dataSource.setUsername(properties.getProperty("db.username"));
dataSource.setPassword(properties.getProperty("db.password"));
// 2、得到连接
// try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
String statementId = "db.sql." + id;
try (Connection conn = dataSource.getConnection();
// 3、获取预处理statement
PreparedStatement ps = conn.prepareStatement(properties.getProperty(statementId))) {
// 4、设置参数
// ps.setObject(1, param);
if (param instanceof Integer || param instanceof String) {
ps.setObject(1, param);
} else if (param instanceof Map) {
Map<String, Object> map = (Map<String, Object>) param;
String columnNames = properties.getProperty(statementId + ".columnNames");
String[] nameArray = columnNames.split(",");
for (int i = 0; i < nameArray.length; i++) {
ps.setObject(i + 1, map.get(nameArray[i]));
}
} else {
// TODO
}
// 5、执行SQL
try (ResultSet rs = ps.executeQuery()) {
// 6、遍历结果集
String resultType = properties.getProperty(statementId + ".resultType");
Class<?> resultTypeClazz = Class.forName(resultType);
while (rs.next()) {
Object result = resultTypeClazz.newInstance();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 0; i < columnCount; i++) {
String columnName = metaData.getColumnName(i + 1);
Field field = resultTypeClazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(result, rs.getObject(columnName));
}
results.add((T) result);
}
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return results;
}
}
MyBatis框架V2版本
- properties配置文件升级为XML配置文件;使用面向过程思维去优化代码;使用面向对象思维去理解配置文件封装的类的作用。
- 需求分析:
- 主要的类和接口:
- Debug 查看解析后的SqlSource信息
配置文件解析阶段
全局配置文件解析
- 利用dom4j从 <configuration> 根标签开始层层解析,将所有的全局配置信息封装到一个 Configuration 对象中。
- Configuration 对象主要封装了DataSource数据源信息,以及解析Mapper映射文件时生成的一个一个的MappedStatement对象集合(key是statementId,value是MappedStatement对象)。
Mapper映射文件解析
-
MappedStatement:封装了select|update|delete|insert标签信息,主要包括 statementId、statementType、resultType、SqlSource等。
-
SqlSource:它是一个接口,其封装了statement标签中的整个SQL信息,并对外提供安全可靠的访问方法(获取JDBC可以正常执行的SQL语句)。
BoundSql getBoundSql(Object param)
- SqlSource的实现类根据处理#{}和${}的不同,分为多个:
- DynamicSqlSource:主要是封装动态SQL标签解析之后的SQL语句和带有${}的SQL语句
- RawSqlSource:主要封装带有#{}的SQL语句
- StaticSqlSource:是BoundSql中要存储SQL语句的一个载体,上面两个SqlSource的SQL语句,最终都会存储到该SqlSource实现类中。
-
BoundSql:主要封装了解析之后JDBC可以直接执行的SQL语句,以及#{}解析出来的参数信息List <ParameterMapping>
-
SqlNode:它是一个接口,其封装的是不同的SQL片段的信息,并提供对封装数据的操作
void apply(DynamicContext context)
。- SqlSource与SqlNode的关系:一个SqlSource包含多个SqlNode。
- SqlNode的实现类:
- TextSqlNode:封装了带有${}的 SQL文本。
- StaticTextSqlNode:封装了不带有${}的 SQL文本。
- IfSqlNode:封装了带有if标签的动态标签。
- MixedSqlNode:封装的是一组SqlNode,采用的是组合模式。
- 对SQL片段的解析过程,用到了责任链模式,最终会形成一个基于SqlNode的树状结构。
-
DynamicContext:它是对SqlNode执行过程中需要的信息进行统一的封装传递,对于解析之后的SQL语句进行字符串拼接。
执行阶段
- 根据statementId从Configuration对象中获取mappedStatement对象,并从 mappedStatement 得到SqlSource对象,
sqlSource.getBoundSql
会触发 SqlSource 和 SqlNode 的解析处理流程。 - 进过解析处理会返回 BoundSql 对象,从 BoundSql 中获取 JDBC 可以正常执行的SQL,同时从 BoundSql 中获取入参信息,利用合适的 Statement 给SQL设置参数,最后执行SQL。
代码图解
MyBatis框架V3版本
MyBatis架构流程图
全局配置文件和映射文件中的四个解析类
- XMLConfigBuilder:解析全局配置文件
- XMLMapperBuilder:解析Mapper映射文件
- XMLStatementBuilder:解析 select|insert|update|delete 标签的
- XMLScriptBuilder:解析 SqlSource
SqlSession执行流程中的四大组件
-
Executor接口
- CachingExecutor:二级缓存执行器
- BaseExecutor(抽象类):一级缓存执行器
- SimpleExecutor:普通的JDBC执行
- BatchExecutor:处理批量操作
- ReuseExecutor:statement可重用
-
StatementHandler接口
- PreparedStatementHandler:预编译PreparedStatement的处理器
- SimpleStatementHandler:处理简单JDBC的Statement的处理器
- CallableStatementHandler:支持存储过程的Statement的处理器
- RoutingStatementHandler:用于路由选择上面三种Statement处理器
-
ParameterHandler接口
- DefaultParameterHandler
-
ResultSetHandler接口
-
代码github地址:https://github.com/shouwangyw/ssm/tree/main/mybatis-framework-custom