MyBatis源码解析

Posted Code_BinBin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis源码解析相关的知识,希望对你有一定的参考价值。

MyBatis源码解析

MyBatis是一种ORM框架(object relational mapping)ORM也叫对象关系映射,用于实现面向对象编程语言里不同类型系统的数据之间的转换,在没有MyBatis之前,我们用的是JDBC,有了MyBatis之后,就开始用MyBatis了

之前

在这里插入图片描述

之后

在这里插入图片描述

拆解

当我们没有使用MyBatis的时候,连接数据库用的是JDBC,我们需要获取数据库源,然后写执行语句,最后再操作

在这里插入图片描述

解析配置文件并生成SqlSessionFactory

首先,我们知道,MyBatis是需要写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核心配置文件-->
<configuration>

    <!--    日志-->
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <typeAliases>

        <typeAlias type="com.znb.pojo.User" alias="User"/>
        <typeAlias type="com.znb.pojo.Student" alias="Student"/>
        <typeAlias type="com.znb.pojo.Teacher" alias="Teacher"/>
        <typeAlias type="com.znb.pojo.Student1" alias="Student1"/>
        <typeAlias type="com.znb.pojo.Teacher1" alias="Teacher1"/>
        <typeAlias type="com.znb.pojo.Blog" alias="Blog"/>

    </typeAliases>

    <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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="1212121"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/userMapper.xml"></mapper>
        <mapper resource="mapper/studentMapper.xml/"></mapper>
        <mapper resource="mapper/teacherMapper.xml/"></mapper>
        <mapper resource="mapper/studentMapper1.xml/"></mapper>
        <mapper resource="mapper/teacherMapper1.xml/"></mapper>
        <mapper resource="mapper/BlogMapper.xml/"></mapper>
    </mappers>

</configuration>

并且,还需要写好工具类来获取MyBatis的连接

public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //使用Mybatis第一步:获取sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
    // SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }

}

写一个测试类,看看MyBatis是否配置成功

  @Test
    public void test() {
        //第一步:获得SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();


        //方式一:getMapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();

        for (User user : userList) {
            System.out.println(user);
        }

        //关闭SqlSession
        sqlSession.close();
    }

在这里插入图片描述

我们点进SqlSessionFactoryBuilder看一下源码,发现MyBatis会按照以下顺序执行build方

第一

 public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

第二

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.
      }
    }
  }

第三

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

第四

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

这个时候会进入parse()这个方法,进行判断,如果parse的值是true,则表示已经加载了配置,如果不是,则没有加载配置

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

这个时候就会调用parseConfiguration,他是把每个配置文件的属性放在Configuration的属性里面,这里要留意environmentsElement(),mapperElement(),databaseIdProviderElement(),propertiesElement()等方法

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);
    }
  }

这个是解析properties标签,读取我们引入的外部配置文件。这里面又有两种类型,一种是放在resource 目录下的,是相对路径,一种是写的绝对路径的。解析的最终结果就是我们会把所有的配置信息放到名为defaults 的Properties 对象里面,最后把XPathParser 和Configuration 的Properties 属性都设置成我们填充后的Properties对象。

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

这里调用了settingsElement()方法,这里是设置MyBatis给我们开启的工能,如果配置里面写了,那么就会替换,否自就会用默认参数,这里就不细讲了,可以去官网看一下

private void settingsElement(Properties props) {
    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")));
  }

mapperElement是一个解析mapper的方法

private void mapperElement(XNode parent) throws Exception {
		// 看到这里了
		if (parent != null) {
			// 开

以上是关于MyBatis源码解析的主要内容,如果未能解决你的问题,请参考以下文章

mybatis源码解析一

Mybatis源码解析MyBatis解析Mapper.xml(以动态sql为例)

mybatis源码解析

MyBatis 源码解析:SQL 语句的执行机制

MyBatis 源码解析

mybatis源码解析 - mapper代理对象的生成