解上篇文章 MyBatis 第一题
结合前面的 MyBatis Demo,其核心代码如下
//xml配置文件路径
String resource = "mybatis-config.xml";
//读取配置
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过 SqlSessionfactory 获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//通过 SqlSession 获取 Mapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://172.31.32.184:3306/constxiong?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="constxiong@123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="constxiong/mapper/UserMapper.xml"/>
</mappers>
</configuration>
这种是配置解析 xml 文件的方式,通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory 实例,从 SqlSession 中获取 UserMapper 接口。
从源码角度看,大致流程
1、通过流获取 xml 文件,构建 SqlSessionFactory 对象
使用 XPath 库解析 xml 配置文件
其中包含 mappers、mapper 标签的解析,获取 mapper 标签的 source 参数
使用 XMLMapperBuilder 对象解析根据 resource 参数定位的 Mapper xml 文件,包含 namespace、cache-ref、* cache、parameterMap、resultMap、sql、select、insert、update、delete 标签的解析以及它们节点上的参数
在 configuration 对象中注册 namespace 及其对应 Mapper 接口 Class 对象的代理工厂对象 MapperProxyFactory
Mapper 接口中的方法上的注解解析,转换为对应的 Statement 与 ResultMap
2、通过 SqlSession 对象获取 UserMapper 的代理对象 MapperProxy
configuration 对象中 mapperRegistry 获取对应的 MapperProxyFactory
MapperProxyFactory 对象实例化 Mapper 接口的代理对象 MapperProxy
3、执行 UserMapper 的接口方法时,调用代理对象 MapperProxy 的 invoke 方法,执行相应的 SQL
MapperProxy 的 invoke 方法,构建了 PlainMethodInvoker 对象
调用 PlainMethodInvoker 对象的 invoke 方法,委托给 MapperMethod 属性的 execute 方法执行相应的 SQL,返回结果
还有一种通过硬编码的方式,构建 Configuration 对象,注册 UserMapper 接口,Demo 中也给出示例了,总体流程差别不大。
回到之前列出的第一个问题
Mapper 接口如何与写 SQL 的 XML 文件进行绑定的?
先给出答案:
Mapper 接口与 XML 文件的绑定是通过 XML 里 mapper 标签的 namespace 值与 Mapper 接口的 包路径.接口名 进行绑定
Mapper 接口的方法名与 XML 文件中的 sql、select、insert、update、delete 标签的 id 参数值进行绑定
其中涉及到了 MappedStatement 的 id、SqlCommand 的 name 的值为 Mapper 接口的 包路径.接口名.方法名
详细分析
要点 1、Mapper 接口与 XML 文件的绑定是通过 XML 里 mapper 标签的 namespace 值与 Mapper 接口的 包路径.接口名 进行绑定
源码体现在 XMLMapperBuilder 的 bindMapperForNamespace 方法
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
要点 2、Mapper 接口的方法名与 XML 文件中的 sql、select、insert、update、delete 标签的 id 参数值进行绑定
源码体现在两个部分1)生成 id 与 MappedStatement 对象注册到 configurationXMLMapperBuilder configurationElement 方法中
//sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//select、insert、update、delete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
XMLMapperBuilder sqlElement 方法中
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
XMLStatementBuilder parseStatementNode 方法中
//获取 Mapper xml 中标签 id
String id = context.getStringAttribute("id");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
MapperBuilderAssistant addMappedStatement 方法中
id = applyCurrentNamespace(id, false);
MapperBuilderAssistant applyCurrentNamespace 方法中
return currentNamespace + "." + base;
MapperBuilderAssistant addMappedStatement 方法中,最后把 MappedStatement 注册到 configuration 对象中
configuration.addMappedStatement(statement);
2)根据 Mapper 接口方法查到并调用对应的 MappedStatement,完成绑定MapperProxy cachedInvoker 方法创建 PlainMethodInvoker 对象,创建了 MapperMethod 对象
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
MapperMethod 对象的 SqlCommand 中的 name 属性根据解析设置为对应的 MappedStatement 的 id
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);
name = ms.getId();
MapperMethod execute 方法 SqlCommand 类型,通过 sqlSession 根据 SqlCommand 的 name(上一步被设置为 对应的 MappedStatement 的 id) 找到 MappedStatement 执行 select、insert、update、delete
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
.
.
.
return result;
}
}
更多相关文章
- 详解第三种创建线程的方式-Callable接口
- Spring Ioc 实例化 Bean 对象有几种方式?
- Javascript面向对象入门
- 【编测编学】接口测试面试题必背(下)
- 如何在 Java 中构造对象(学习 Java 编程语言 034)
- 快速测试 API 接口的新技能
- java创建对象的过程(内存角度分析)