Mybatis源码解析之数据源和SQL构建

文章目录

        • 1、Mybatis官网简介
        • 2、Demo搭建
          • 2.1、maven依赖
          • 2.2、mybatis配置
          • 2.3、mapper文件配置
          • 2.3、测试类
        • 3、源码解析
          • 3.1、初始化阶段
          • 3.2、获取数据源
          • 3.3、构建SQL语句
          • 3.4、解析mybatis-config.xml过程总结
        • 4、常见面试题
        • 5、MyBatis数据库连接和SQL执行

1、Mybatis官网简介

  • MyBatis一款半自动的ORM持久层框架,它支持自定义SQL、存储过程以及高级映射。
  • MyBatis对JDBC进行了封装,几乎免除了所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis可以通过简单的XML(mapper文件)或注解两种方式操作数据库。

MyBatis的执行流程图大致如下:
Mybatis源码解析之数据源和SQL构建_第1张图片

2、Demo搭建

Mybatis的Demo可以直接去官网下载,如果不下载的话那么可以根据下面的步骤搭建一个Demo,测试MyBatis
源码。

2.1、maven依赖

在maven项目中的pom文件引入Mybatis依赖,pom依赖项如下所示:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.2.1</version>
</dependency>
2.2、mybatis配置

新建一个mybatis-config.xml文件作为Mybatis文件配置,这个配置文件对应mybatis包下面的Configuration类,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<settings>
  <!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存-->
  <setting name="cacheEnabled" value="true"/>
  <!--延迟加载的全局开关,查询时,关闭关联对象即时加载以提高性能-->
  <setting name="lazyLoadingEnabled" value="true"/>
  <!--是否允许单个语句返回多结果集-->
  <setting name="multipleResultSetsEnabled" value="true"/>
  <!--使用列标签代替列名-->
  <setting name="useColumnLabel" value="true"/>
  <!--允许JDBC支持自动生成主键-->
  <setting name="useGeneratedKeys" value="false"/>
  <!--指定 MyBatis 应如何自动映射列到字段或属性-->
  <!--NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。
   FULL 会自动映射任何复杂的结果集(无论是否嵌套)--> 
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <!--配置默认的执行器->
  <!--SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); 
  BATCH 执行器不仅重用语句还会执行批量更新-->
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <!--当没有为参数指定特定的 JDBC 类型时,空值的默认JDBC 类型->
  <setting name="jdbcTypeForNull" value="OTHER"/>
</settings>

    <!--定义SqlSession的环境配置-->
    <environments default="development">
        <environment id="development">
            <!--定义事务管理器配置-->
            <transactionManager type="JDBC"/>
            <!--数据源配置 采用连接池-->
            <dataSource type="POOLED">
                <!--jdbc驱动器-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--mysql的url-->
                <property name="url" value="实际路径"/>
                <!--用户名-->
                <property name="username" value="实际用户名"/>
                <!--密码-->
                <property name="password" value="实际密码"/>
            </dataSource>
        </environment>
    </environments>
    
    <!--配置mapper文件-->
    <mappers>
        <mapper resource="DemoMapper.xml"/>
    </mappers>

</configuration>

本文demo的Mybatis配置文件只配置了以下三个内容,实际使用当中这三个配置也比较常用,具体如下所示:

  • settings”标签描述的是MyBatis自身的一些属性配置,比如缓存和延迟加载等一系列属性,在这里配置的属性会覆盖掉Mybatis包下面的Configuration类的默认配置。
  • environments”标签配置的数据库操作相关的,比如事务管理配置、连接池配置、数据源配置。
    • 事务管理配置,MyBatis自身提供了两种事务管理器,分别为JDBC和MANAGED。
      • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域
      • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。
    • 连接池配置,MyBatis共提供了三种连接池,分别如下:
      • UNPOOLED–这个数据源的实现会每次请求时打开和关闭连接。
      • POOLED–这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
      • JNDI–这个数据源实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
    • 数据源配置就是用来连接数据库的,包括驱动器、URL、用户名和密码。
  • mapper文件里面存放的是SQL语句,但是我们需要告诉MyBatis去哪找到这些SQL文件,所以mappers标签就是告诉MyBatis去哪找这些SQL语句。MyBatis提供了四种方法映射mapper文件,分别如下:

下面的demo是摘抄自官网,实际中应根据项目的具体路径配置

<!-- 第一种使用相对于类路径的资源引用 也是本文中使用的方式-->
<mappers>
 <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>

<!-- 第二种使用相对于类路径的资源引用 -->
<mappers>
   <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>

<!-- 第三种使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

<!-- 第四种使用映射器接口实现类的完全限定类名 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

根据官网介绍,MyBatis配置文件共有9个配置标签可供使用,具体如下:
Mybatis源码解析之数据源和SQL构建_第2张图片

2.3、mapper文件配置

DemoMapper接口内容如下:

public interface DemoMapper {
    /**
     * 查询用户信息
     *
     * @return 返回结果
     */
    List<BaseUserInfoDO> queryBaseUserInfoDO();
}

此次测试中选择的是base_user_info数据库表,本次只测试select查询语句,然后分析其底层源码实现。DemoMapper.xml文件内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC
        "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.DemoMapper">
 <!--用as将数据表中的字段映射到实体类字段上-->  
 <select id="queryBaseUserInfoDO" resultType="com.test.entity.BaseUserInfoDO">
        /**UserMapper.queryBaseUserInfoDO 用户信息查询*/
        SELECT
            id as id,
            user_id as userId,
            user_name as userName,
            phone as phone,
            status as status,
            sign_status as signStatus,
            sex as sex 
        FROM
        base_user_info
    </select>
</mapper>

对应接收的实体类如下:

@Getter
@Setter
@ToString(callSuper = true)
public class BaseUserInfoDO implements Serializable {
    private   Long     id;
    private   Long     userId;
    private   String   userName;
    private   String   phone;
    private   String   status;
    private   String   signStatus;
    private   String   sex;
}
2.3、测试类

测试类代码如下:

public class TestMyBatis {

    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        //获取MyBatis配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //通过SqlSessionFactoryBuilder的build()方法获取SqlSessionFactory 对象
        //此时已将MyBatis配置文件的相关配置已获取到,即Configuration类已获取到值
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory的openSession()方法获取SqlSession 对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //通过SqlSession对象执行SQL语句
            List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}

Demo搭建过程如下:
Mybatis源码解析之数据源和SQL构建_第3张图片
Demo运行结果如下:
Mybatis源码解析之数据源和SQL构建_第4张图片
从上图中可以看到,MyBatis的的整个执行流程,解析如下:

  • 第一步创建数据库连接
  • 第二步使用连接,执行SQL语句并返回结果
  • 第三步关闭数据库连接,将关闭后的连接放回到连接池中

3、源码解析

终于到了源码分析阶段了,相信到了这一步,你应该非常想看到Myabtis到底是如何解析SQL语句的吧。不要急,慢慢来,心急可吃不了热豆腐哦。

通过官网的介绍,可以知道MyBatis的作用是用来连接数据库,然后执行SQL语句的,根据Demo的执行结果也验证了官网的正确性。所以此次源码分析主要分析这两大块内容,

  • MyBatis如何获取数据源的?
  • MyBatis是如何构建SQL语句的?
3.1、初始化阶段

根据Demo的内容可以知道,数据源和配置类信息都配置在mybatis-config.xml文件中,在测试类TestMyBatis 中,先是将配置文件解析成InputStream流,放到SqlSessionFactoryBuilder的build(inputStream)方法中,然后获取到SqlSessionFactory对象,所以现在就来看一下build()方法源码里面执行了哪些内容吧。 即解析下面这条语句的源码

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

源码执行过程如下所示。部分无关代码已省略:

public class SqlSessionFactoryBuilder {
  
 /**
 * build方法的入口,调用下面的build方法
 **/
 public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  /**
  * 在这里真正的对mybatis-config.xml文件进行解析
  **/
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
      //这一步中主要的操作是将mybatis-config.xml文件加载进parser对象,然后初始化Configuration类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //先调用parser.parse()方法获取mybatis-config.xml文件配置信息,然后将里面的信息赋值给Configuration类
      //然后调用下面的build方法,将已经赋值的Configuration类作为参数传递进DefaultSqlSessionFactory类中
      //这一步中Configuration类已经完全获取mybatis-config.xml文件配置信息
      return build(parser.parse());
   }
  /**
  *最后调用的返回方法
  **/
  public SqlSessionFactory build(Configuration config) {
    //将已经初始化的Configuration类作为参数传递进DefaultSqlSessionFactory类,并返回
    return new DefaultSqlSessionFactory(config);
  }
}

将上面的步骤拆开来分析,先看下面这个语句,

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

从名字就可以看出来,这个类的初始化应该是去加载mybatis-config.xml文件,但是还没有真正的去解析,下面看一下这个XMLConfigBuilder类的这个构造函数:

public class XMLConfigBuilder extends BaseBuilder {
  private boolean parsed;
  private XPathParser parser;
  private String environment;
  
  /**
  * 这个方法主要是加载mybatis-config.xml文件
  **/
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) 
  {
    //初始化XPathParser对象,加载mybatis-config.xml文件
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment,     props);
  }
  /**
  * 这个方法主要通过super(new Configuration())方法将初始化的Configuration类传递进去。
  * 主要是将Configuration类进行初始化
  **/
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //调用父类的构造函数,同时Configuration类进行初始化
    //非常关键
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
}

先来看一下XMLConfigBuilder 类的父类BaseBuilder 类的构造函数吧,主要是看 super(new Configuration())这一个语句的一个执行内容。

public abstract class BaseBuilder {
  //MyBatis的配置类
  protected final Configuration configuration;
  //JAVA类型别名对象,主要对应JAVA类型。如整型、布尔型、Float类型等
  protected final TypeAliasRegistry typeAliasRegistry;
  //JDBC类型处理器,主要是将JDBC类型转换成JAVA类型,如将CHAR、VARCHAR类型转化为String类型
  protected final TypeHandlerRegistry typeHandlerRegistry;
  //super(new Configuration())调用的就是这个方法,
  //其实主要的就是Configuration类的初始化  
  public BaseBuilder(Configuration configuration) {
    //初始化后的Configuration类进行赋值
    this.configuration = configuration;
    //JAVA类型别名对象随着Configuration类的初始化也进行初始化了
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    //JDBC类型处理器随着Configuration类的初始化也进行初始化了
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
}  

下面看一下Configuration 类中成员变量吧,几乎整个MyBatis的配置数据以及存储数据都是通过Configuration类进行读和写的。 下面看一下Configuration类型主要成员变量,无关的成员变量暂时省略。

/**
* Configuration类包含了mybatis-config.xml中所有的配置选项
**/
public class Configuration {
  //Environment对象对应的就是mybatis-config.xml文件中environments标签
  //对应的就是数据库的事务管理和数据源配置
  protected Environment environment;
  //下面所有的变量对应的都是mybatis-config.xml文件中setting标签中的配置
  //settings标签映射开始
  protected boolean safeRowBoundsEnabled = false;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase = false;
  protected boolean aggressiveLazyLoading = true;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys = false;
  protected boolean useColumnLabel = true;
  //MyBaits一级缓存默认是开启
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls = false;
  protected String logPrefix;
  protected Class <? extends Log> logImpl;
  protected ProxyFactory proxyFactory;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  //默认执行器SIMPLE类型的,共有三种,其它两种是REUSE、BATCH
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected Properties variables = new Properties();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);
  //延时加载默认关闭
  protected boolean lazyLoadingEnabled = false;
  //settings标签映射结束


  //数据库ID
  protected String databaseId;
  //驱动器
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  //默认拦截器
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  //JAVA类型别名对象 进行实例化
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  //JDBC类型处理器 进行实例化
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  //对应SQL操作对象,用Map进行存储
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
   //mapper文件的节点容器
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
  //SQL语句查询结果的缓存容器,就是查询一次后,再次查询相同的SQL语句,可以从此处直接获取缓存数据
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  //SQL语句执行完成后返回的结果集
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  //SQL语句的参数容器
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  //mapper文件(存放SQL语句)中SQL语句构造器容器
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  //SQL语句的主键容器
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
  protected final Set<String> loadedResources = new HashSet<String>();
  //缓存解析器容器
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  //JDBC结果解析器容器
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  //方法解析器容器
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

  //Configuration对象的默认构造函数
  //这个构造函数的主要作用是向JAVA类型别名对象中增加一些JAVA类型对象
  public Configuration() {
    //...已省略部分代码
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  }
}

JAVA类型别名对象TypeAliasRegistry初始化内容如下:

/**
 * 这个类只展示部分代码,主要展示它的内容,理解其功能
 **/
public class TypeAliasRegistry {
  //用Map来存储类型别名和对应的JAVA类型
  private final HashMap<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
  public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
  }
  //registerAlias方法,主要是将JAVA类型别名和对应的JAVA类型放进Map容器中
 public void registerAlias(String alias, Class<?> value) {
    //省略无用代码.......
   
    //就是将JAVA类型的别名和对应的类传到TYPE_ALIASES这个Map容器中
    TYPE_ALIASES.put(key, value);
  }
}

JDBC类型处理器对象TypeHandlerRegistry,在查询完SQL语句之后会调用这个类将JDBC类型映射成对应的JAVA类型,它的初始化内容如下:

/**
 * 这个类只展示部分代码,主要展示它的内容,理解其功能
 **/
public final class TypeHandlerRegistry { 
  //JDBC类型处理器容器
  private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
 /**
 * 初始化时将JDBC类型对应的类型处理器存放在Map容器中
 **/
  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());
    
    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(String.class, new StringTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());

    //.......省略部分代码
  }
//....register函数就是将对应的Map中存储对应的类型和处理器对象

至此,下面的整个语句已经解析结束。

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

此时,Configuration类初始化结束,mybatis-config.xml配置文件也加载到parser对象,parser对象的内容截图如下:
Mybatis源码解析之数据源和SQL构建_第5张图片
其中

  • parser对象存储的是mybatis-config.xml配置文件
  • environment对象目前是空,但是执行完之后,它里面保存的是事务管理器和数据源
  • configuration对象存储的是MyBatis默认的初始化配置,执行完之后,配置文件的内容会覆盖默认配置
  • typeAliasRegistry存储的是JAVA类型别名对象
  • typeHandlerRegistry存储的是JDBC类型处理器对象
3.2、获取数据源

从上面的内容可以知道,mybatis-config.xml配置文件存储在parser对象中, 但是还没有进行解析, 那么接下来看下面这个函数是怎么解析mybatis-config.xml文件的吧

parser.parse()

这个方法的具体内容如下所示:

public class XMLConfigBuilder extends BaseBuilder {
 /**
 * parse()方法入口
 **/
 public Configuration parse() {
    //如果已解析,则抛一个已解析的异常出去,
    if (parsed) {
      throw new BuilderException("Each MapperConfigParser can only be used once.");
    }
    //将是否已解析的标志字段置为true,开始解析配置文件了
    parsed = true;
    //parser.evalNode("/configuration")方法是获取mybatis-config.xml文件中的标签
    //...配置信息....中的所有内容
    //parseConfiguration()方法则开始对中间的所有标签进行解析
    parseConfiguration(parser.evalNode("/configuration"));
    //返回结果
    return configuration;
  }
  /**
  * 开始对mybatis-config.xml文件进行解析
  **/
  private void parseConfiguration(XNode root) {
    try {
      //从这里就可以可以看到这些属性其实就是对应mybatis-config.xml文件中的所有配置标签
      //这个方法执行完成之后就可以获取到mybatis-config.xml文件中的所有信息了
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      //解析JAVA类型别名类标签
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析拦截器标签
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析settings标签
      settingsElement(root.evalNode("settings"));
      //解析environments标签即获取事务管理和数据源
      environmentsElement(root.evalNode("environments")); 
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //JDBC结果处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mapper文件标签 获取SQL语句
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
}

此次测试类主要下面两个方法:

  • environmentsElement()方法:获取事务管理和数据源
  • mapperElement()方法:获取SQL语句的StateMent

environmentsElement() 方法源码如下:

public class XMLConfigBuilder extends BaseBuilder {
  /**
  * 方法入口:解析environments标签内容,获取数据源配置
  **/ 
  private void environmentsElement(XNode context) throws Exception {
    //节点内容肯定不为空
    if (context != null) {
       //如果environment标签内容为空,则获取默认配置
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      //循环遍历environments标签下的所有节点,知道找到environment标签的ID
      for (XNode child : context.getChildren()) {
        //获取当前节点的ID
        String id = child.getStringAttribute("id");
        //environment标签的id与当前节点的ID匹配
        if (isSpecifiedEnvironment(id)) {
          //获取事务管理器
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          //获取数据源
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          //设置configuration对象中的Environment对象
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }
}

方法执行完成之后可以看到如下结果
Mybatis源码解析之数据源和SQL构建_第6张图片
environmentsElement() 方法至此结束,此时已经获取到了数据源和事务管理配置了。

3.3、构建SQL语句

接下来看mapperElement() 方法里面是如何获取SQL语句的,即获取StateMent对象,这个过程有点复杂,因为mapper文件中的sql一般如下所示:

<mapper namespace="com.mybatis.DemoMapper">
<select id="queryBaseUserInfoDO" resultType="com.test.entity.BaseUserInfoDO">
        /**UserMapper.queryBaseUserInfoDO 用户信息查询*/
        SELECT
            id as id,
            user_id as userId,
            user_name as userName,
            phone as phone,
            status as status,
            sign_status as signStatus,
            sex as sex 
        FROM
        base_user_info
    </select>
</mapper>

一个StateMent对象其实就是一个完整的SQL语句,要拼接完整的StateMent内容,需要先获取