Mybatis源码分析之SqlSessionFactory的创建

Mybatis源码分析(一)

Mybatis的运行过程主要分为两步,第一步读取配置文件将配置缓存到Configuration对象,用于构建SqlSessionFactory,第二步为SqlSession的执行过程。其中SqlSession的过程会比较难,而第一步相对来说比较容易看懂,相对简单点。

以普通案例开始

1     @Test
2     public void findById() throws Exception {
3         String resource = "mybatis-config.xml";
4         InputStream inputStream = Resources.getResourceAsStream(resource);   //构建一个输入流对象
5         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  //在该地方打断点来分析
6         SqlSession sqlSession = sessionFactory.openSession();
7         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
8         System.out.println(userMapper.findById(1));
9     }

SqlSessionFactory构建的过程

首先进入到了SqlSessionFactoryBuilder类中的build方法中来

1 public SqlSessionFactory build(InputStream inputStream) {
2     return build(inputStream, null, null);
3   }

该方法中调用了另外一个build方法

 1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 2     try {
 3       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 4       return build(parser.parse());
 5     } catch (Exception e) {
 6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
 7     } finally {
 8       ErrorContext.instance().reset();
 9       try {
10         inputStream.close();
11       } catch (IOException e) {
12         // Intentionally ignore. Prefer previous error.
13       }
14     }
15   }

我们看到了一个可疑对象XMLConfigBuilder类,继续跟踪进去

1 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
2     this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
3   }

猜测这里应该是做XML校验的,跳过,创建好了XMLConfigBuilder对象,接下来进行parse()了

1 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
2 return build(parser.parse());

继续跟踪进入XMLConfigBuilder中的parse()

1 public Configuration parse() {
2     if (parsed) {
3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
4     }
5     parsed = true;
6     parseConfiguration(parser.evalNode("/configuration"));  
7     return configuration;
8   }

跟进该类的parseConfiguration方法中

 1 private void parseConfiguration(XNode root) {
 2     try {
 3       //issue #117 read properties first
 4       propertiesElement(root.evalNode("properties"));
 5       Properties settings = settingsAsProperties(root.evalNode("settings"));
 6       loadCustomVfs(settings);
 7       loadCustomLogImpl(settings);
 8       typeAliasesElement(root.evalNode("typeAliases"));
 9       pluginElement(root.evalNode("plugins"));
10       objectFactoryElement(root.evalNode("objectFactory"));
11       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
12       reflectorFactoryElement(root.evalNode("reflectorFactory"));
13       settingsElement(settings);
14       // read it after objectFactory and objectWrapperFactory issue #631
15       environmentsElement(root.evalNode("environments"));      //在这里读取数据源的信息封装
16       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
17       typeHandlerElement(root.evalNode("typeHandlers"));
18       mapperElement(root.evalNode("mappers"));              //这里是解析我们定义的Mapper.xml
19     } catch (Exception e) {
20       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
21     }
22   }

看到这里应该舒服了吧 这不就是我们的mybatis-config配置文件的内容吗。

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE configuration
 3         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 <!-- 根标签 -->
 6 <configuration>
 7     <properties></properties>
 8     <settings>
 9         <setting name="cacheEnabled" value="true"/>
10     </settings>
11     <typeAliases></typeAliases>
12     <typeHandlers></typeHandlers>
13     <objectFactory type=""></objectFactory>
14     <objectWrapperFactory type=""></objectWrapperFactory>
15     <reflectorFactory type=""></reflectorFactory>
16     <plugins>
17         <plugin interceptor=""></plugin>
18     </plugins>
19     <!-- 环境,可以配置多个,default:指定采用哪个环境 -->
20     <environments default="test">
21         <!-- id:唯一标识 -->
22         <environment id="test">
23             <!-- 事务管理器,JDBC类型的事务管理器 -->
24             <transactionManager type="JDBC" />
25             <!-- 数据源,池类型的数据源 -->
26             <dataSource type="POOLED">
27                 <property name="driver" value="com.mysql.jdbc.Driver" />
28                 <property name="url" value="jdbc:mysql://127.0.0.1:3306/ssmdemo" />
29                 <property name="username" value="root" />
30                 <property name="password" value="123456" />
31             </dataSource>
32         </environment>
33     </environments>
34     <databaseIdProvider type=""></databaseIdProvider>
35     <mappers>
36         <mapper resource="mappers/UserMapper.xml" />
37     </mappers>
38 </configuration>

接下来我们细看一下mapperElement方法

 1 private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         //如果是package的方法加载则进入package分支
 5         if ("package".equals(child.getName())) {
 6           String mapperPackage = child.getStringAttribute("name");
 7           configuration.addMappers(mapperPackage);
 8         } else {
 9           String resource = child.getStringAttribute("resource");
10           String url = child.getStringAttribute("url");
11           String mapperClass = child.getStringAttribute("class");
12            //如果是resource的方法加载则进入resource分支
13           if (resource != null && url == null && mapperClass == null) {
14             ErrorContext.instance().resource(resource);
15             InputStream inputStream = Resources.getResourceAsStream(resource);
16             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
17             mapperParser.parse();
18              //如果是url的方法加载则进入url分支
19           } else if (resource == null && url != null && mapperClass == null) {
20             ErrorContext.instance().resource(url);
21             InputStream inputStream = Resources.getUrlAsStream(url);
22             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
23             mapperParser.parse();
24             //进入class分支
25           } else if (resource == null && url == null && mapperClass != null) {
26             Class<?> mapperInterface = Resources.classForName(mapperClass);
27             configuration.addMapper(mapperInterface);
28           } else {
29             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
30           }
31         }
32       }
33     }
34   }

Mybatis的mapper.xml有四种加载方式

1 <mappers>
2     <mapper resource="mappers/UserMapper.xml" />
3     <mapper class=""></mapper>
4     <mapper url=""></mapper>
5     <package name=""></package>
6 </mappers>

所以上面的代码是在选择哪种方式加载mapper.xml文件。我们定义的是resource方式,所以进入resource分支中去。

 1 if (resource != null && url == null && mapperClass == null) {
 2     ErrorContext.instance().resource(resource);
 3     InputStream inputStream = Resources.getResourceAsStream(resource);
 4     XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //构建mapper解析器
 5     mapperParser.parse();  //解析mapper.xml的逻辑开始
 6   } 
 7   
 8   //进入XMLMapperBuilder类找那个的方法
 9   public void parse() {
10     if (!configuration.isResourceLoaded(resource)) {  //如果是resource加载进入
11       configurationElement(parser.evalNode("/mapper"));
12       configuration.addLoadedResource(resource);
13       bindMapperForNamespace();   //通过namespace绑定mapper
14     }
15 
16     parsePendingResultMaps();          //解析resultMap标签
17     parsePendingCacheRefs();           //解析cache-ref标签
18     parsePendingStatements();          //其他的标签
19   }

执行完这些之后,将这些配置文件全部封装在configuration对象中了,接着返回该对象进入SqlSessionFactoryBuilder的build方法中

 1 public SqlSessionFactory build(Configuration config) {
 2     return new DefaultSqlSessionFactory(config);
 3   }
 4   
 5   public class DefaultSqlSessionFactory implements SqlSessionFactory {
 6 
 7   private final Configuration configuration;
 8 
 9   public DefaultSqlSessionFactory(Configuration configuration) {
10     this.configuration = configuration;
11   }
12   ......
13  }

可以看到DefaultSqlSessionFactory是SqlSessionFactory的一个实现类,里面包含了Configuration对象。到这里已经构建完成了SqlSessionFactory了

总结

SqlSessionFactory的构建过程Configuration类起了很大作用,几乎所以的配置都能在该类中找到。有兴趣的同学可以去看看。

上一篇:掌握MyBatis的核心对象


下一篇:Revo Uninstaller Pro - 真正彻底卸载软件不留垃圾的强大神器!(清理安装残留文件/注册表)