入口实例:
下面是简单的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 <SysUser> sysUsers = sysUserMapper.queryList();
sqlSession.commit();
}finally {
sqlSession.close();
}
}
}
为了更清楚Mybatis实现的原理流程,我们伴随着问题渐渐进入:
- Mybaits 加载后什么时候,以及怎么将配置文件(mybatis-config.xml 和 mapper.xml)里面的信息加入和处理的?
- 动态sql又是怎么处理的?
- 应用中我们定义了mapper接口,在没有实现类的情况下,为什么可以通过调用接口方法执行sql查询?
- Mybatis是怎么进行的数据库连接 和s ql查询的?底层原理是什么?
- 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内容的方式有四种:
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> </mappers><!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> </mappers><!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> </mappers><!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
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<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator<XMLStatementBuilder> 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);
- 通过 XPathParser解析器解析配置文件流,并创建Document,最终封装成 XMLConfigBuilder类。
- 通过XMLConfigBuilder.parse解析配置文件信息,主要对configuration标签以及其二级标签解析,并将解析结果存入Configuration中,最后解析mapper.xml文件,遍历其中的sql标签,并最终存储到mappedstatment中,mappedstatment也会被存储到configuration中。(所以Configuration是存储了配置文件的全部信息)。
- 最终返回 DefaultSqlSessionFactory实现类,其属性为 Configuration。
所以改句代码核心工作就是解析配置文件xml,并封装为Configuration,返回DefaultsqlSessionFactory。