Mybatis源码解析(一)

76 阅读5分钟

1、基本简介

image.png

上图是mybatis的整体架构,对比JDBC来看

   public static void main(String[] args) throws Exception {
       //1 - 注册驱动
        Class.forName("com.mysql.jdbc.Driver");

       //2 - 获取数据库连接
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/springdemo", "root", "123");

       //3 - 获取语句执行平台
        PreparedStatement statement = 
            con.prepareStatement("select * from employee where id = ?");

       //4 - 设置参数
        statement.setString(1,"8");//给?添加参数
       
       //5 - 执行,并返回结果
        ResultSet resultSet = statement.executeQuery();

       //6 - 处理结果
        while (resultSet.next()) {
            System.out.println(resultSet.getString("id"));
        }

       //7 - 关闭执行平台与数据库连接
        statement.close();

        con.close();
    }
  • 框架主要优化了以下方面的事情
  1. 为sql设置参数
  2. 处理数据库返回的结果,简化了处理过程
  3. 数据库的连接由框架进行管理
  4. 提供了更强大的动态sql能力

接下来根据下面的Demo来解析框架的大体流程
阅读框架要抓大放小,梳理清楚整体脉络即可,框架的大部分代码都是针对细节进行处理

public static void main(String[] args) throws Exception {
  
  //加载配置文件
  SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("MapperConfig.xml"));

  //创建sqlSession
  SqlSession sqlSession = sqlSessionFactory.openSession();

  List<StudentPOJO> studentPOJOS = null;

  //1.通过动态代理对象执行
  Demo2 demo2 = sqlSession.getMapper(Demo2.class);
  studentPOJOS = demo2.selectAll(new HashMap());

  //2.根据statement执行
  studentPOJOS = sqlSession.selectList("org.apache.ibatis.zDemo.Demo2.selectAllByStu", new Object());

  System.out.println(JSONObject.toJSONString(studentPOJOS));
  System.out.println("end");
}

其他准备工作

  1. 配置文件
<?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>
    <property name="lizhi" value="lizhi"/>
  </properties>

  <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="localCacheScope" value="STATEMENT"/>
  </settings>
  <plugins>
    <plugin interceptor="org.apache.ibatis.zDemo.ExamplePlugin"></plugin>
  </plugins>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="org.apache.ibatis.zDemo.myDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://********:22206/study_demo"/>
        <property name="username" value="root"/>
        <property name="password" value="88888"/>
        <property name="initialSize" value="10"/>
        <property name="maxActive" value="20"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <!--    <package name="org.apache.ibatis.zDemo"/>-->
    <mapper resource="mapper/DemoMapper.xml"/>
    <mapper resource="mapper/DemoMapper2.xml"/>
  </mappers>

</configuration>

2、加载配置文件

  • 首先框架也是读取读取xml配置文件 image.png

  • 读取configuration标签中的内容 image.png

private void parseConfiguration(XNode root) {
  try {
    //1.处理properties标签
    propertiesElement(root.evalNode("properties"));

    //2.加载settings标签(没有其他处理)
    Properties settings = settingsAsProperties(root.evalNode("settings"));

    //2.1.处理vfs实现类
    loadCustomVfs(settings);

    //2.2.处理log实现类
    loadCustomLogImpl(settings);

    //2.3 处理类型别名
    typeAliasesElement(root.evalNode("typeAliases"));

    //3.处理插件
    pluginElement(root.evalNode("plugins"));

    //4.是否指定了三个工厂类的实现
    objectFactoryElement(root.evalNode("objectFactory"));//用来创建对象
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));//创建对象映射
    reflectorFactoryElement(root.evalNode("reflectorFactory"));

    //5.处理其他setting属性
    settingsElement(settings);

    //6.处理环境信息(处理数据源,事务管理)
    environmentsElement(root.evalNode("environments"));

    //7.
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));

    //8.处理类型处理器
    typeHandlerElement(root.evalNode("typeHandlers"));

    //9.处理映射文件mapper
    mapperElement(root.evalNode("mappers"));

  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

处理properties标签

  1. 主要是将properties属性读取,添加到配置文件对象中
  • 三种配置方式
<properties resource="db.properties"></properties>

<properties url="http://www.baidu.com/db.properties"></properties>

<properties>
    <property name="lizhi" value="lizhi"/>
</properties>

处理settings标签

  • 示例
  <settings>
    <setting name="cacheEnabled" value="false"/>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="localCacheScope" value="STATEMENT"/>
  </settings>
  1. 读取setting标签内容
  2. 通过反射获取Configuration.class类的set方法,对比标签内容是否正确
  3. 缓存属性值

处理vfs实现类

  • 基本没用过

处理日志实现类

  1. 从setting中找到logImpl属性值
  2. 找到对应的calss类
  3. 赋值给configuration

处理typeAliases标签

  • 为类名指定别名
<!--为类名指定别名-->
<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

<!--为包名指定别名-->
<typeAliases>
  <package name="domain.blog"/>
</typeAliases>
  • 将包名或类型的别名与全程名称注册到TypeAliasRegistry对象中

处理插件

<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>
  • plugins标签标注的插件,加入configuration的插件链中

三个工厂实现类

      objectFactoryElement(root.evalNode("objectFactory"));//用来创建对象
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));//创建对象映射
      reflectorFactoryElement(root.evalNode("reflectorFactory"));

设置configuration的默认值

  • 优先使用xml中设置的属性值
  • 如果没有设置,则使用默认值
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));

处理环境信息

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)) {
        //创建事务工厂,JdbcTransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //创建数据源工厂,默认UnpooledDataSourceFactory
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
          .transactionFactory(txFactory)
          .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}

这里跟着配置文件和配置详解来看

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="org.apache.ibatis.zDemo.myDataSource">
      <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://********:22206/study_demo"/>
      <property name="username" value="root"/>
      <property name="password" value="88888"/>
      <property name="initialSize" value="10"/>
      <property name="maxActive" value="20"/>
    </dataSource>
  </environment>
</environments>
1. default - 指定使用哪个环境信息
2. transactionManager - 事务的管理方式
   1. type
      1. JDBC - 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用
      2. MANAGED - 它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期
3. dataSource - 数据库连接相关信息
   1. type
      1. UNPOOLED - 直连数据库
      1. POOLED - 使用数据库连接池
      1. JNDI - 
  1. 找到环境信息列表environments,标签内会指定默认的环境
  2. 为默认的环境信息创建TransactionFactory, DataSourceFactory
    1. 初始化TransactionFactory对象
    2. 初始化DataSourceFactory
      1. 将数据库各种信息写入对象中
    3. DataSourceFactory中获取DataSource
  3. 创建Environment对象,持有DataSourceFactory,DataSource
  4. 将环境信息加入configuration中

环境信息对象详解


#### 事务相关对象
  1. TransactionFactory - 事务管理器工厂
实现类解释
JdbcTransactionFactory直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用
ManagedTransactionFactory它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期

  1. Transaction - 由TransactionFactory事务工厂产生
实现类解释
JdbcTransaction事务对象 - 包含数据源,隔离级别,事务是否自动提交
ManagedTransaction

数据库连接相关对象

  1. DataSourceFactory - 数据源接口
实现类解释其他
JndiDataSourceFactory
UnpooledDataSourceFactory直连数据库
PooledDataSourceFactory使用连接池技术继承UnpooledDataSourceFactory
自定义数据源工厂使用德鲁伊数据库连接池继承PooledDataSourceFactory,构造函数new DruidDataSource数据源赋值给dataSource

  1. DataSource - DataSourceFactory 数据源工厂产生
实现类解释其他
PooledDataSource连接池数据源
UnpooledDataSource非连接池数据源
DruidDataSource德鲁伊数据源

处理数据库厂商标识

  • 解析所有databaseIdProvider节点的属性,然后通过上面配置的数据源,创建一个数据库连接来获取数据库产品的名称(使用完就关掉),然后于节点的name属性匹配,将value值设置到configuration中

类型处理器

  • 主要是为后面的sql出参入参的类型处理使用
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>
  • 将配置的类型处理器加入到TypeHandlerRegistry中

下一章主要介绍重要的mapper映射文件的处理过程