Mybatis的建构图:
(说明:最近在重新学习一下mybatis,然后因为自己也是初学者,写博客来记录自己学习的点滴,如有错误或者不恰当的地方,欢迎指出,,,也希望大佬们多多指教)
参考之前那篇mybatis的helloworld的博客,开始简单分析执行过程:
public class DemoTest {
/**
*获得sessionFactory
* 1.getSqlSessionFactory()获得SqlSessionFactory
* 2.sqlSessionFactory.openSession()获得session
* 3.sqlSession.getMapper(DepartmentMapper.class)获得mapper
* 4.mapper.getDepartmentById(1) 执行查询操作。
*
*/
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
@Test
public void test() throws IOException {
//获得sqlSessionFactory
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 获得Sqlsession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获得mapper代理对象
DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
// 执行方法
Department departmentById = mapper.getDepartmentById(1);
// 输出结果
System.out.println(departmentById);
}
}
下面主要分析代码的这四个步骤:
* 1.getSqlSessionFactory()获得SqlSessionFactory
* 2.sqlSessionFactory.openSession()获得session
* 3.sqlSession.getMapper(DepartmentMapper.class)获得mapper
* 4.mapper.getDepartmentById(1) 执行查询操作。
getSqlSessionFactory()
简述说一下执行过程:
1.先获得mybatis-config.xml文件输入流
2.执行SqlSessionFactoryBuilder().build(inputStream)
进入SqlSessionFactoryBuilder的build方法
接着看bulid方法:
===》
解释一下第一句代码:
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 根据inputStream生成一个XMLConfigBuilder 也就是xml配置的配置建造器, parser就是一个解释器,可以解析 xml文件
return build(parser.parse());
分析:parser.parse() 点进去看看源码,发现parser.parse()返回的是一个configuration对象
如图所示:
而bulider(parser.parse()) 返回的是一个DefaultSqlSessionFactory
如下图所示:
这个就是我们想要获得的SqlSessionFactory 其实他是DefaultSqlSessionFactory
现在开始先看看parser.parse() 的执行过程
1.parseConfiguration(parser.evalNode("/configuration"));
先看这个方法里面的parser.evalNode("/configuration")
这里是计算configuration节点,这个configuration就是我们在mybatis-config.xml当中配置的节点。返回值是** XNode**,
2.拿到了我们配置文件当中configuration的节点信息之后,开始执行
parseConfiguration('拿到的configuration节点')具体执行过程如下:
这就是根据configuration根节点,分别获得其子节点,然后对子节点的信息进行解析。
如最后一行:
看到解析mapper标签节点的这个例子mapperElement(root.evalNode("mappers"));
1.首先root.evalNode("mappers")
通过根节点,获得mappers
节点的信息
2.执行mapperElement(root.evalNode("mappers"))
执行过程如下图:
2.1如果父节点不为空执行遍历操作
for (XNode child : parent.getChildren())
获得的所有的子节点
if ("package".equals(child.getName()))
如果获得mapper使用的是package属性例如:
配置了<package name="com.kuake.dao.mapper"></package>
那么String mapperPackage = child.getStringAttribute("name");
//获得name的值
接着 configuration.addMappers(mapperPackage)
// 把mapperPackage的信息设置到configuration当中,
如果是设置了 resource
如: <mapper resource="com/kuake/dao/mapper/DepartmentMapper.xml"/>
,那么执行下面的代码
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//获得resources的输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//获得xml文件的解析器,跟之前类似
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 开始解析xml文件
mapperParser.parse();
}
** 重点看mapperParser.parse()方法**
第一步.configurationElement(parser.evalNode("/mapper"));
//先获得mapper节点 ,然后保存节点的信息
代码如下:
private void configurationElement(XNode context) {
try {
//当命名空间为null或空串的话,抛出异常
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
上面代码的作用:获得对应节点的属性并且进行设置。
以buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
为例子,走一下他代码的过程。:
- context.evalNodes(“select|insert|update|delete”)获得所有增删改查,节点。
- 执行 buildStatementFromContext(‘增删改查节点信息’)
** buildStatementFromContext()方法代码如下**
先进行判断configuration当中是否有getDatabaseId()没有执行,然后执行 buildStatementFromContext(list, null);
代码如下:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//获得statementParser 解析器,遍历,,每一个节点,都会生成一个statementParser
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//statementParser添加到configuration当中
configuration.addIncompleteStatement(statementParser);
}
}
}
上述代码的主要功能如下:
1.解析每一个增删改查节点
2.new XMLStatementBuilder(…)方法获得statementParser
3.statementParser.parseStatementNode();解析节点
4.statementParser添加到configuration当中添加到configuration当中
第三点重点是的parseStatementNode()代码如下;
这个方法的作用就是,把该节点的所有信息那取出来 如:fetchSize,timeout,parameterType,resultMap…等等,然后调用appMappedStatement(),就是上图中的最后一个方法,作用是生成一个mappedStatement,保存了每一个sql节点的所有信息:
appMappedStatement()代码如下:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
上面代码的主要几个步骤如下:
1.根据传入解析出的标签属性,创建statementBuilder
构建器
2. MappedStatement statement = statementBuilder.build();
//构建器出MappedStatement
3. configuration.addMappedStatement(statement);
//将MappedStatement 保存在configuration当中。
解析mapper.xml文件小结:mapper标签信息解析,保存到全局配置文件中,其每一个增删改查方法的每一个属性都解析出来,然后调用上述的addMappedStatement(每一个属性),生成一个MappedStatement ,并且保存在configuration当中。
保存了statement之后,遍开始返回了。那么mybatis-config.xml
当中的mapepr节点所引入的mapper.xml文件也就解析完成,等mybatis-config.xml
这个配置文件的所有子节点都解析完成之后。所以的信息都已经保存在了configration当中,然后执行到下面这一段代码,传入了configration,就返回了我们所需要的DefaultSqlSessionFctory也就是SqlSessionFactory.
为了思路整理一下,加深记忆:画一下了获得sqlSessionFactory的简单时序列图
小结:
把所有配置文件的配置信息(mybatis-config.xml,xxxmapper.xml)保存在Configuration当中,然后调用DefaultSqlSession的构造方法,返回DefaultSqlSessionFactory,这也就是我们需要的SqlSessionFactory,其中Configuration包含了一个属性mapperStatement,就是解析每一个sql节点,所生成的,包含了sql节点的所有信息,
下面两幅图是debug时,,Configuration与mapperStatement所包含的信息。
mapperStatement:包含了每一个增删改查sql的全部配置信息。如我们所首先熟悉的:
Configuration:包含全局配置信息,也就是mybatis-config.xml,的信息,和mapperStatement