Mybatis3.5.4源码分析-1-解析配置文件

1.阅读源码准备工作

下载源码,以及mybatis-parent,对源码进行编译,详见 MyBatis如何给源码加中文注释 一文

2.测试代码

通过分析测试代码,带着几个疑惑开始阅读源码

package com.gupaoedu;

import com.gupaoedu.domain.associate.AuthorAndBlog;
import com.gupaoedu.domain.Blog;
import com.gupaoedu.domain.associate.BlogAndAuthor;
import com.gupaoedu.domain.associate.BlogAndComment;
import com.gupaoedu.mapper.BlogMapper;
import com.gupaoedu.mapper.BlogMapperExt;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class MyBatisTest {
    
    

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void prepare() throws IOException {
    
    
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 创建工厂类:解析配置文件,经过了什么样的过程,得到了什么样的结果
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }
    /**
     * 通过 SqlSession.getMapper(XXXMapper.class)  接口方式
     * @throws IOException
     */
    @Test
    public void testSelect() throws IOException {
    
    
        // 通过工厂类获取到SqlSession,这里有两个问题: 1. SqlSession是一个接口,这里返回的到底是什么样的一个实现 2、创建SqlSession实现类的时候,又创建了哪些对象
        SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
        try {
    
    
            // BlogMapper到底是个什么样的对象,为什么传递进去一个BlogMapper,然后又返回一个BlogMapper
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            // BlogMapper既然是一个接口,那么是无法去调用方法的,这里的mapper必然是BlogMapper的一个具体的实现类,那么它真正的执行方法是什么样的
            Blog blog = mapper.selectBlogById(1);
            System.out.println(blog);
        } finally {
    
    
            session.close();
        }
    }
}

从上面代码可以看出,mybatis主要工作流程包含4个步骤

  • 解析配置文件
  • 创建工厂类
  • 创建会话
  • 通过会话操作数据库

3.源码分析

1. 配置读取与解析

 // 所有构造都调用这一个真正的build方法
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    
    
    try {
    
    
      // XMLConfigBuilder用来解析全局配置文件
      // 为了解析不同的配置文件,还有其他的XML**Builder,比如XMLMapperBuilder是用来解析 mapper.xml映射器文件的
      // XMLStatementBuilder是用来解析映射器文件中的增删改查标签的
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // 先进入build方法,parser.parse()返回的是一个Configuration对象
      return build(parser.parse());
    } catch (Exception e) {
    
    
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
    
    
      ErrorContext.instance().reset();
      try {
    
    
        reader.close();
      } catch (IOException e) {
    
    
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

在这里插入图片描述
对于不同类型的内容,交给不同的builder去解析;
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
进入XMLConfigBuilder构造函数:

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    
    
    // 解析之前,先把Configuration对象创建好,用来保存xml配置文件根标签<configuration>的属性 
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

返回org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)
方法,重点看parser.parse() 方法,点击进入,看到一个parseConfiguration方法:

  public Configuration parse() {
    
    
    if (parsed) {
    
    
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // DMO SAX都有用到
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

parseConfiguration方法如下:

private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      //issue #117 read properties first
      // 解析全局配置文件中的每个标签
      propertiesElement(root.evalNode("properties"));
      // 解析settings标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      // 设置日志实现类
      loadCustomLogImpl(settings);
      // 类型别名,之所以可以直接使用string这种类型别名,是因为TypeAliasRegistry类中已经 定义好了
      typeAliasesElement(root.evalNode("typeAliases"));
      // 插件
      pluginElement(root.evalNode("plugins"));
      // 对象工厂:把结果集封装成对象时,只能通过反射来创建对象并给对象属性赋值
      objectFactoryElement(root.evalNode("objectFactory"));
      // 用于对对象进行特殊的包装
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工具箱
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // settings 子标签赋值,默认值就是在这里提供的;前面只是解析,并未赋值
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 创建数据源
      environmentsElement(root.evalNode("environments"));
      // 数据库厂商
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 类型转换器: JDBC类型与java类型之间的双向类型转换
      typeHandlerElement(root.evalNode("typeHandlers"));
      // mapper.xml映射文件的路径
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

其中的关键点在于以下几个方法

  • settingsElement(settings) 设置mybatis的默认配置
  • environmentsElement(root.evalNode(“environments”)); 创建数据源
  • typeHandlerElement(root.evalNode(“typeHandlers”)); 类型转换器的配置,完成Java类型与数据库字段类型的双向转换,对于一个字段在两种数据类型做相互转换,mybatis用了一个 private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); 来保存这种类型映射关系
  • mapperElement(root.evalNode(“mappers”)); 解析mapper.xml映射器文件
 private void  environmentsElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      if (environment == null) {
    
    
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
    
    
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
    
    
          // 事务工厂
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 数据源工厂
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          // 数据源
          DataSource dataSource = dsFactory.getDataSource();
          // 包含了事务工厂和数据源的Environment
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 全部设置到Configuration对象中
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

解析mapper:

private void mapperElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
    
      for (XNode child : parent.getChildren()) {
    
    
        // 不同的定义方式的扫描,最终都调用addMapper方法添加到mapperRegistry
        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");
          if (resource != null && url == null && mapperClass == null) {
    
    
            // resource 相对路径
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析Mapper.xml
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
    
    
            // url绝对路径
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
    
    
            // class接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 最终都会调者addMapper方法
            configuration.addMapper(mapperInterface);
          } else {
    
    
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

关键方法在 mapperParser.parse() :
这里很关键
第一,解析出来的sql语句放到了哪个对象里面(sql的注册),涉及到后面使用sql时如何取
第二,注册namespace对应的接口,注册时做了哪些事情

  public void parse() {
    
    
    // 总体上做两件事情:对增删改查语句的注册 和namespace对应的接口的注册
    if (!configuration.isResourceLoaded(resource)) {
    
    
      // 具体增删改查标签解析
      // 重点!!!  一个标签对应一个MappedStatement
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 把namespace接口类型和工厂类绑定,放到一个map中   一个namespace一个 MapperProxyFactory
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
private void configurationElement(XNode context) {
    
    
    try {
    
    
      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"));
      // 解析cache标签,添加缓存对象,只有加了cache标签才会去解析,也就是说,namespace下默认是没有二级缓存的
      cacheElement(context.evalNode("cache"));
      // 创建ParameterMapping对象
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//      创建List<ResultMapping>
      resultMapElements(context.evalNodes("/mapper/resultMap"));
//      解析可复用的sql
      sqlElement(context.evalNodes("/mapper/sql"));
//      重点:解析增删改查标签,得到MappedStatement, 后续使用简写都为ms
      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: 使用XMLStatementBuilder 解析增删改查sql标签

  private void buildStatementFromContext(List<XNode> list) {
    
    
    if (configuration.getDatabaseId() != null) {
    
    
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
//    解析Statement
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    
    
    for (XNode context : list) {
    
    
      // 用来解析增删改查标签的XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
    
    
        // 解析Statement,添加MappedStateMent对象
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
    
    
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

statementParser.parseStatementNode();
-----> org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
-----> org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)
返回一个MappedStatement对象,这个对象保存了增删改查标签中的各种属性的值:
在这里插入图片描述
可以看到sql语句被解析到了MappedStatement对象中,那么 对于namespace中的接口,是如何解析的呢?回到org.apache.ibatis.builder.xml.XMLMapperBuilder#parse方法的 bindMapperForNamespace();中:

  private void bindMapperForNamespace() {
    
    
    // namespace就是接口类型(接口类的全路径名)
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
    
    
      Class<?> boundType = null;
      try {
    
    
        // 反射得到接口的类型
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
    
    
        //ignore, bound type is not required
      }
      if (boundType != null) {
    
    
        // 避免重复解析
        if (!configuration.hasMapper(boundType)) {
    
    
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          // 添加到MapperRegistry 本质是个map,里面也有Configuration
          configuration.addMapper(boundType);
        }
      }
    }
  }

addMapper是这样实现的:

 public <T> void addMapper(Class<T> type) {
    
    
    mapperRegistry.addMapper(type);
  }
  public <T> void addMapper(Class<T> type) {
    
    
    // 只扫描接口类型
    if (type.isInterface()) {
    
    
      // 已经解析过,直接抛出异常
      if (hasMapper(type)) {
    
    
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
    
    
        // Map<Class<?>,MapperProxyFactory<?>>存放的是接口类型和该接口对应的工厂类的关系
        //MapperProxyFactory.newInstance 返回接口的代理实现类!!!
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 注册了接口之后,根据接口,开始解析所有方法上的注解如@Select
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析注解!
        parser.parse();
        loadCompleted = true;
      } finally {
    
    
        if (!loadCompleted) {
    
    
          knownMappers.remove(type);
        }
      }
    }
  }

knownMappers : private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
上面的创建过程,可以总结如下:
在这里插入图片描述
回到org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties) 方法,
这个方法最终返回的是一个DefaultSqlSessionFactory对象

E:\resources-compile\mybatis-resources
E:\Baidudownload\2020期课程资料\02.架构师审美观\06.MyBatis原理篇\01.MyBatis应用分析与最佳实践\课堂源码\mybatis-standalone\

猜你喜欢

转载自blog.csdn.net/weixin_41300437/article/details/108589289