1. 前言
接触Mybatis框架有些时日了,但我仅仅处于“会使用它”的这个层面,并没有对它进行深入地理解。所以,现在抽时间想对它一探究竟。
2. Mybatis工作原理
Mybatis工作原理参考了此博文:
Mybatis工作原理
2.1 Mybatis架构
2.1.1 Mybatis框架的分层
Mybatis框架大致分为四层:引导层、接口层、数据处理层、框架支撑层。
如下图:
2.1.2 Mybatis框架的实现原理
Mybatis的底层还是采用了原生JDBC技术来实现对数据库的操作。只是通过 SqlSessionFactory,SqlSession,Executor,StatementHandler,ParameterHandler,ResultHandler和TypeHandler等几个处理器封装了这些过程。
几个组件的功能如下:
- Executor:执行器(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler:参数处理器(getParameterObject, setParameters)
- ResultSetHandler:结果处理器(handleResultSets, handleOutputParameters)
- StatementHandler:sql查询处理器(prepare, parameterize, batch, update, query)
其中,StatementHandler通过ParameterHandler与ResultHandler分别进行参数预编译 与结果处理。而ParameterHandler与ResultHandler都使用TypeHandler进行映射。
2.2 Mybatis的核心运行流程
Mybatis的核心运行流程可分为三个阶段:
- 初始化阶段:读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化工作
- 代理阶段:封装Mybatis的编程模型,使用Mapper接口开发的初始化工作
- 数据读写阶段:通过SqlSession完成SQL的解析、参数的映射、SQL的执行、结果的反射解析过程
3. 手写Mybatis
按照Mybatis的核心运行流程的三个阶段一步步编码,每完成一个阶段,就进行测试。
3.1 初始化阶段
读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化工作
项目结构图:
1. MappedStatement
MappedStatement对象是对mapper文件中的每一个标签进行抽象
@Data
public class MappedStatement {
// 命名空间
private String namespace;
// id
private String resourceId;
// 返回类型
private String resultType;
// sql语句
private String sql;
}
2. Confifuration
mapper文件中包含多个标签,所有会有个对象来存储标签的集合
Confifuration是用来存储所有配置信息的,且此配置信息全局唯一
/**
* 存储所有配置信息:
* 数据库配置信息、mapper配置信息
*/
@Data
public class Configuration {
// jdbc的驱动
private String jdbcDriver;
// jdbc的url
private String jdbcUrl;
// jdbc的username
private String jdbcUsername;
// jdbc的password
private String jdbcPassword;
/**
* mapper文件中可能有多条SQL语句(MapperStatement对象)
* Map:可实现快速访问
*/
private Map<String, MappedStatement> mapperStatements = new HashMap<>();
}
3. SqlSessionFactory
我们在使用Mybatis时,会写如下代码:
在通过工厂模式进行实例化SqlSessionFactory时,会加载mybatis的核心配置文件mybatis-config.xml。所以,这里也仿照mybatis源码,新建个SqlSessionFactory类
public class SqlSessionFactory {
private final Configuration configuration = new Configuration();
public SqlSessionFactory() {
loadDbInfo();
loadMappersInfo();
}
...
}
此类有两个作用:
- 实例化过程中加载配置文件到Configuration对象
- 生产SqlSession对象
loadDbInfo()
// 记录mapper.xml文件存放的位置
public static final String MAPPER_CONFIG_LOCATION = "mappers";
// 记录数据库连接信息文件存放的位置
public static final String DB_CONFIG_FILE = "db.properties";
// 加载数据库配置信息
private void loadDbInfo() {
// 加载数据库信息配置文件
InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
Properties p = new Properties();
try {
// 将配置信息写入到Properties对象
p.load(dbIn);
} catch (IOException e) {
e.printStackTrace();
}
// 将数据库配置信息写入到Configuration对象
configuration.setJdbcDriver(p.get("jdbc.driver").toString());
configuration.setJdbcUrl(p.get("jdbc.url").toString());
configuration.setJdbcUsername(p.get("jdbc.username").toString());
configuration.setJdbcPassword(p.get("jdbc.password").toString());
}
loadMappersInfo()
// 加载指定文件夹下的所有mapper.xml文件
private void loadMappersInfo() {
URL resource = null;
resource = SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
// 获取指定文件夹信息
File mappers = new File(resource.getFile());
if (mappers.isDirectory()) {
File[] files = mappers.listFiles();
// 遍历文件夹下所有的mapper.xml,并解析信息后,注入到Configuration对象中
for (File file : files) {
loadMapperInfo(file);
}
}
}
loadMapperInfo()
使用dom4j技术对mapper文件进行解析
// 加载指定的mapper.xml文件
private void loadMapperInfo(File file