mybatis源码系列(一)—— mybatis启动配置加载

首先看一下我们用到的示例代码

InputStream inputStream = new FileInputStream(new File("src/main/resources/mybatis-config.xml"));
// 1.加载配置文件创建configuration对象,创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取Mapper接口动态代理
BaseTermMapper mapper = sqlSession.getMapper(BaseTermMapper.class);
// 4.动态代理回调sqlSession中的查询方法,sqlSession将查询方法转发给Executor,
// Executor基于JDBC访问数据库获取数据并通过反射将数据转换成POJO并返回
BaseTerm baseTerm = mapper.selectById(1);
System.out.println(baseTerm.getName());
sqlSession.close();

配置文件具体代码:
在这里插入图片描述
db.properties:

jdbc_driver=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://xx
jdbc_username=root
jdbc_password=xxx
project_src =src/main/java
project_mapper_xml =src/main/sqlmapper
class_path=C:/Users/Administrator/.m2/repository/mysql/mysql-connector-java/5.1.47/mysql-connector-java-5.1.47.jar

mybatis-config.xml:

<?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>
 <properties resource="db.properties"/>
   <settings>
  <!-- 设置自动驼峰转换    -->
  <setting name="mapUnderscoreToCamelCase" value="true" />
  <!-- 开启懒加载 -->
   <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。默认:true -->
   <setting name="aggressiveLazyLoading" value="false" />
 </settings>
 <!-- 别名定义 -->
 <typeAliases>
  <package name="com.kennor.mybatis.entity" />
 </typeAliases>
 <!--配置environment环境 -->
 <environments default="development">
  <!-- 环境配置1,每个SqlSessionFactory对应一个环境 -->
  <environment id="development">
   <transactionManager type="JDBC" />
   <dataSource type="POOLED">
    <property name="driver" value="${jdbc_driver}" />
    <property name="url" value="${jdbc_url}" />
    <property name="username" value="${jdbc_username}" />
    <property name="password" value="${jdbc_password}" />
   </dataSource>
  </environment>
 </environments>
 <!-- 映射文件,mapper的配置文件 -->
 <mappers>
  <!--直接映射到相应的mapper文件 -->
  <mapper resource="sqlmapper/BaseTermMapper.xml"/>
 </mappers>
</configuration>

BaseTermMapper.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.kennor.test.BaseTermMapper" >
  <!--开启二级缓存 走源码调试用 生产环境不推荐开启-->
  <cache/>
  <resultMap id="BaseTermMap" type="com.kennor.test.BaseTerm">
    <id property="id" column="id"/>
    <result property="name" column="NAME"/>
    <result property="beginTime" column="begin_time"/>
    <result property="endTime" column="end_time"/>
  </resultMap>
  <select id="selectById" resultMap="BaseTermMap">
     select *
        from base_term
        where  id = #{id}
  </select>
</mapper>

1.加载配置文件创建configuration对象,创建SqlSessionFactory
Configuration : Mybatis启动初始化的核心就是将所有xml配置文件信息加载到Configuration对象中, Configuration是单例的,生命周期是应用级的;
加载配置文件的核心类主要有:
XMLConfigBuilder: 主要负责解析mybatis-config.xml;
XMLMapperBuilder: 主要负责解析映射配置文件;
XMLStatementBuilder: 主要负责解析映射配置文件中的SQL节点;
对应的类图:
在这里插入图片描述
Mapper相关的关键类主要有:
MapperRegistry:mapper接口动态代理工厂类的注册中心。在MyBatis中,通过mapperProxy实现InvocationHandler接口,MapperProxyFactory用于生成动态代理的实例对象;
ResultMap:用于解析mapper.xml文件中的resultMap节点,使用ResultMapping来封装id,result等子元素;
MappedStatement:用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包含了这些节点的很多重要属性;
SqlSource:mapper.xml文件中的sql语句会被解析成SqlSource对象,经过解析SqlSource包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;

具体源码如下:
在这里插入图片描述
在这里插入图片描述
parser.paser()方法解析成功后会返回一个Configuration对象,此对象包含配置文件的信息,然后build方法通过Configuration创建SqlSessionFactory对象
在这里插入图片描述
接着parser.paser()的具体代码如下:
在这里插入图片描述
在这里插入图片描述解析数据库连接信息properties的具体代码如下:
在这里插入图片描述
其他解析都是类似的将配置读取出来,然后设置到configuration对象中。
接着我们看一下映射文件mappers节点的解析流程
在这里插入图片描述
在这里插入图片描述
接着我们看一下mapperParser.parse()是如何解析mapper.xml文件的
在这里插入图片描述
解析mapper节点的具体代码如下:
在这里插入图片描述
首先看一下解析缓存节点代码,如下:
在这里插入图片描述
在这里插入图片描述
除了配置的缓存功能之外,CacheBuilder在build缓存对象的时候会为其附加上标准的功能,代码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接着我们看一下resultMap节点的解析流程
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
接着我们先看一下ResultMap的源码有什么字段
在这里插入图片描述
然后再看数据是如何解析保存到ResultMap中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public ResultMap build() {
  if (resultMap.id == null) {
    throw new IllegalArgumentException("ResultMaps must have an id");
  }
  resultMap.mappedColumns = new HashSet<>();
  resultMap.mappedProperties = new HashSet<>();
  resultMap.idResultMappings = new ArrayList<>();
  resultMap.constructorResultMappings = new ArrayList<>();
  resultMap.propertyResultMappings = new ArrayList<>();
  final List<String> constructorArgNames = new ArrayList<>();
  for (ResultMapping resultMapping : resultMap.resultMappings) {
    resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
    resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
    // 获取column
    final String column = resultMapping.getColumn();
    if (column != null) {
      resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
    } else if (resultMapping.isCompositeResult()) {
      for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
        final String compositeColumn = compositeResultMapping.getColumn();
        if (compositeColumn != null) {
          // 将column转换成大写保存到mappedColumns中
          resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
        }
      }
    } 
    // 获取property
    final String property = resultMapping.getProperty();
    if (property != null) {
      // 将property保存到mappedProperties中
      resultMap.mappedProperties.add(property);
    }
    if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
      resultMap.constructorResultMappings.add(resultMapping);
      if (resultMapping.getProperty() != null) {
        constructorArgNames.add(resultMapping.getProperty());
      }
    } else {
      resultMap.propertyResultMappings.add(resultMapping);
    }
    if (resultMapping.getFlags().contains(ResultFlag.ID)) {
      // 保存ID的映射Mapping
      resultMap.idResultMappings.add(resultMapping);
    }
  }
  if (resultMap.idResultMappings.isEmpty()) {
    resultMap.idResultMappings.addAll(resultMap.resultMappings);
  }
  if (!constructorArgNames.isEmpty()) {
    final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
    if (actualArgNames == null) {
      throw new BuilderException("Error in result map '" + resultMap.id
          + "'. Failed to find a constructor in '"
          + resultMap.getType().getName() + "' by arg names " + constructorArgNames
          + ". There might be more info in debug log.");
    }
    resultMap.constructorResultMappings.sort((o1, o2) -> {
      int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
      int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
      return paramIdx1 - paramIdx2;
    });
  }
  // lock down collections
  // 使用Collections.unmodifiableList方法实现对解析结果集合进行加锁,防止被改动
  resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
  resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
  resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
  resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
  resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
  return resultMap;
}

自此,mapper.xml中的resultMap节点就已经解析完成并保存到configuration中。
接着我们继续看一下select、insert、update、delete节点是如何解析的。
在这里插入图片描述
在这里插入图片描述

public void parseStatementNode() {
  //id="selectById"
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
  //获取节点名称 select、delete、update、insert
  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  ... ...
  //获取SqlSource,此类封装了sql语句和查询条件
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  ... ...
  //创建MappedStatement
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

接着我们看一下SqlSource是如何创建的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接着继续看一下创建MappedStatement的流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
自此,可以看到配置文件中的关键信息都已经解析储存到configuration中,并且使用configuration创建了DefaultSqlSessionFactory。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值