Mybatis3 官方文档
入口实例:
下面是简单的mybatis入口实例代码,一个简单的查询操作:
SysUser 查询结果的实体类、SysUserMapper为dao层的mapper接口,方法queryList()
public class MyMain {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
SysUserMapper sysUserMapper = sqlSession.getMapper( SysUserMapper.class );
List sysUsers = sysUserMapper.queryList();
sqlSession.commit();
}finally {
sqlSession.close();
}
}
}
为了更清楚Mybatis实现的原理流程,我们伴随着问题渐渐进入:
想要弄清楚上述的问题原理,势必要源码解析,先简单说下Mybatis 源码中几个核心的关键词(接口、类、方法),方便分析时留意:
SqlSessionFactory -- 接口 : sqlSession工厂,内部存储了Configuration,实现类为DefaultSqlSessionFactory
sqlSessionFactoryBuilder # build : 实现配置文件的解析,并构建了Configuration,返回DefaultSqlSessionFactory
Configuration : 存储了配置文件的全部信息
SqlSession -- 接口 : 实现类DefaultSqlSession存储了Configuration、executor
Executor : sql执行器,内部具体实现后面再看
MappedStatement : mapper.xml解析之后,具体的执行标签 (比如select标签)的内存存储
StatementHandler: 处理sql语句预编译,设置参数的相关工作。
ParameterHandler: 设置预编译参数。
ResultSetHandler: 处理结果集
TypeHandler : 在执行过程中进行数据库类型和javaBean类型的映射
源码分析
该篇从SqlSessionFactory说起。重上面的案例代码中我们看到只有一行代码
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
上面简单提到了其作用的工作内容,我们进入源码查看:
进入SqlSessionFactoryBuilder().build(inputStream) build方法:
// inputStream 是配置文件读取出来的输入流
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 解析数据流,底层使用了XPathParser 解析器解析为Document,将数据最终解析封装为
XMLConfigBuilder(这个不深入)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parse方法,对配置文件解析,深入
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
进入parser方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析configuration标签:configuration为最顶级标签
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
进入parseConfiguration方法:正真来解析配置文件:通过各种Element方法的调用,解析各种标签,我们核心看下 mapperElement(root.evalNode("mappers")); 对mapper的解析,对应配置为下:
// 解析配置文件
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// properties 节点
propertiesElement(root.evalNode("properties"));
// settings 节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// mappers节点(核心)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
深入mapperElement(root.evalNode("mappers")) 对mappers 内容解析:
主要根据不同的引入标签做处理,我们之前配置文件中配置mapper内容的方式有四种:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 判断扫描节点是否是package属性引入的
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 判断扫描节点是否是resource属性引入的
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 跟解析配置文件xml一样原理,这里来解析找到的mapper.xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析mapper
mapperParser.parse();
// 判断扫描节点是否是url属性引入的
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
// 判断扫描节点是否是mapper属性引入的
} else if (resource == null && url == null && mapperClass != null) {
Class> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
我们这边是通过resource引入的,所以重点看下resource中的parse,解析mapper.xml
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper节点处理动态sql语句,后续介绍
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
// 怎么一步步解析mapper.xml 我们不关心了,我们重点看下parsePendingStatements
parsePendingStatements();
}
///===============================parsePendingStatements===================================
private void parsePendingStatements() {
Collection incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator iter = incompleteStatements.iterator();
while (iter.hasNext()) {
try {
// 遍历,解析
iter.next().parseStatementNode();
iter.remove();
} catch (IncompleteElementException e) {
// Statement is still missing a resource...
}
}
}
}
// ======================================parseStatementNode=========================
// 该方法最后调用,核心:这边是将每个执行sql标签解析出来存入 MappedStatement中,所以我们要记住这点,每一个sql执行语句被解析之后,都被存入MappedStatement,所以后面指定语句时需要找到对应的sql语句
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
这边解析工作基本上就看完了,主要是对配置文件的解析,优先configuration标签 ,然后一步步的对其二级标签解析,最后通过mapperparse解析mapper.xml 并将解析出来的sql语句存入mappedstatement中,还有一点没有提到的是,每一个标签解析完都会
configuration.setVariables(defaults); 保存到Configuraction中。
回到一开始,调用完parse方法后,返回的最终返回了 build(parser.parse())值,就是DefaultSqlSessionFactor类,而他就是用来存储Configuration的。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
总结:
SqlSessionFactory sqlSessionFactory= new SqlSessionFactoryBuilder().build(inputStream);
所以改句代码核心工作就是解析配置文件xml,并封装为Configuration,返回DefaultsqlSessionFactory。