Mybatis 源 码 初 解 析 (一)

搁置了好久的mybatis源码攻略,今天来一丢丢小小的总结。
阅读源码吗,我觉得要从宏观到微观的一个顺序去进行。有必要的话甚至可以加上自己的一个图解流程,可以使思路更加清晰。

初想mybatis

  1. 首先mybatis是一个为了简化操作的一个框架
  2. 然后它的存在代替了jdbc连接 那么它里面一定包括着连接数据库的基本操作

关于jdbc的操作

package jdbc;

import java.sql.*;
/**
 * @Author:XK
 * @Date: Created in 17:48 2021/10/26
 * @Description:jdbc的简单实现
 **/
public class jdbcFirstDemo {
    
    
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    
    
        //1加载驱动
        Class.forName("com.mysql.jdbc.Driver");

        //2用户信息和URL
        String url ="jdbc:mysql://localhost:3306/fisrt21?useUnicod=true&characterEncoding=utf8&useSSL=true";
        String username="root";
        String password = "2020";

        //3连接成功 获取到数据库对象,Connection
        Connection connection = DriverManager.getConnection(url,username,password);

        //4.执行sql对象statement 执行sql的对象
        Statement statement = connection.createStatement();

        // 5 . 执行sql对象之后返回的结果集 Resultset
        String sql ="select * from class";

        ResultSet resultSet= statement.executeQuery(sql);

        while (resultSet.next()){
    
    
            System.out.println(resultSet.getString("class_name"));
        }

        //6 释放连接
        resultSet.close();
        statement.close();
        connection.close();

    }
}

我认为有三个部分,数据源,sql语句,数据操作。

  • 这里的数据源就是包括url,username,password的一个配置。通过这个可以连接到数据库
  • sql语句 就是我手动写的一条sql语句
  • 数据操作 就是我connection数据库对象获得sql对象 statement 然后 通过statement 执行sql语句 得到一个结果集的过程。
    这是我觉得很基本的一个jdbc连接数据库的操作。

既然mybats框架代替jdbc连接,那么它也一定完成这三方面的任务,而且顺序也差不多,究竟对不对就要去源码中验证。那么我带着这问题,进入到源码的阅读

mabatis 源码初读

不知道别人是怎么进行一个源码的阅读,对我来说,我就是写好一个demo然后通过idea的debug过程一步一步去看它这个框架究竟是怎么样的一个流程
通过mybatis官方文档 mybatis官方文档
并且从GitHub拉下mybatis的源码。

package test1;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import javax.sound.sampled.Line;
import java.io.IOException;
import java.io.InputStream;


/**
 * @Author:XK
 * @Date: Created in 16:25 2022/4/3
 * @Description:
 **/
public class demo {
    
    
  public static void main(String[] args) throws IOException {
    
    
    String resource = "resources/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    sqlSession.getMapper(UsrMapper.class).selectAll();
  }
}

写下这么一个小demo 开始解析。
首先从第一行代码来说,将config.xml的配置转化为inputstream 而后执行一个sqlsessionFactory的方法的build
熟悉mybatis的都知道,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
这一步就是通过config.xml的解析去构造出一个SqlSessionFactory 的实例,而且它是唯一的。

build方法究竟build了什么 点进去我们看

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    
    
    try {
    
    
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
    
    
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
    
    
      ErrorContext.instance().reset();
      try {
    
    
        inputStream.close();
      } catch (IOException e) {
    
    
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

显然有一个XMLConfigBuilder类,通过类名我能大概猜想出这个玩意是干嘛的,大概就是用来解析xml文件的一个工具类把,显然在build 之前进行了一个XMLConfigBuilder.parse()方法。点进去看

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

  private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

调用了parseConfiguration方法,解析配置 。
看下具体某些方法,我熟悉的就是environments mappers 等 ,点进environmentsElement

  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.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }

从这段代码可以看出来每个字段对应的都对应着一个类,比如environments 对应着一个environment的类

public final class Environment {
    
    
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
  }

这里面的字段正好对应着config.xml的配置

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/fisrt21?useUnicod=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="2020"/>
            </dataSource>
        </environment>
    </environments>

可以看到最后将值拿到之后都会进行一个 configuration.setXXXX ,那么这个configuration究竟是什么

  protected Environment environment;

  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
  protected boolean shrinkWhitespacesInSql;
  protected boolean nullableOnForEach;

发现它包含着这么一系列的配置参数吧属于是,所以猜想他整个mybatis的一些个配置层。
这个过程他把xml文件里的配置全部读取到configuration这个类的实例里面
结束之后 执行build

  public SqlSessionFactory build(Configuration config) {
    
    
    return new DefaultSqlSessionFactory(config);
  }

可以看到返回一个DefaultSqlSessionFactory默认的SqlSessionFactory,里面有selectone或者是getmapper的方法,从这看出来这个mybatis应用的核心SqlSessionFactory已经被创建出一个实例了。验证了我的猜想。
首先config.xml 里的environment就是一个url,以及username,password 的配置。
所以这一步可以说创造了数据源。接下来就是寻找sql语句是如何获取的。

Mybatis 源 码 初 解 析 (二)

猜你喜欢

转载自blog.csdn.net/Yoke______/article/details/123956039