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类起了很大作用,几乎所以的配置都能在该类中找到。有兴趣的同学可以去看看。