mybatis源码过程学习梳理

Posted 天堂1223

tags:

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

一、mybatis配置文件详情

在Spring中,是使用xml的配置文件或使用java代码对mybatis的连接属性,环境等进行配置的,我们先看一下mybatis中对配置文件中节点的要求。
通过mybatis官方网站中XML配置一栏,可以得出相应需求:

  • properties(属性)
  • settings(设置)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境配置)
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
  • databaseIdProvider(数据库厂商标识)
  • mappers(映射器)

我们只有在知道mybatis中的配置中有哪些节点数据,才能够理解mybatis中的解析xml配置的相关代码。
SQL映射文件中只有很少的几个顶级元素:

  • cache - 该命名空间的缓存配置
  • cache-ref - 引用其他命名空间的缓存配置
  • resultMap - 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素
  • sql - 可被其他语句引用的可重用的语句块
  • insert - 映射插入语句
  • update - 映射更新语句
  • delete - 映射删除语句
  • select - 映射查询语句

二、mybatis配置文件解析

在了解了mybatis·1对配置文件的要求之后,以及配置文件中的相应xml节点及配置之后,我们便可以从源码的对XML配置文件的解析,开始了解mybatis的源码内部详情。
我们都知道,在Spring中使用mybatis的时候,需要为Spring提供两个XML文件,一个是beans-mybatis.xml,另外一个是mybatis-config.xml。其中beans-mybatis.xml文件中配置的是Mybatis和Spring结合使用时委托给Spring管理的bean。而mybatis-config.xml中是mybatis的自身的配置。
所以在Spring启动的时候,会通过解析beans-mybatis.xml文件来启动mybatis。我们看一下beans-mybatis.xml的配置文件例子:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml"
        p:mapperLocations="classpath:mapper/**/*.xml" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.cn.kvn.usage.dao" />

其中bean标签指定的Spring容器启动的时候,需要实例化的bean。我们可以看到,启动mybatis的时候,首先启动的是SqlSessionFactoryBean。所以我们看一下在mybatis中SqlSessionFactory。

在mybatis中,SqlSessionFactory是一个接口,使用SqlSessionFactoryBuilder创建SqlSessionFactory实例。我们学习一个SqlSessionFactoryBuilder创建SqlSessionFactory的过程代码:

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

我们通过build中的方法可以看出该方法有三个参数,一个是Reader,一个是字符串类型的环境参数,一个是属性参数。也就是我们在调用该方法的时候,可以传递相关参数信息,同时通过XMLConfigBuilder进行xml格式的解析,创建mybatis运行的基本环境。我们看一下XMLConfigBuilder类。在该类中,通过 parse 方法解析XM配置文件并生成Configuration对象。
我们先看一下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>
    <!-- 1.properties属性引入外部配置文件 -->
    <properties resource="config/db.properties">
        <!-- property里面的属性全局均可使用 -->
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="123456"/>
        <!-- 启用默认值特性,这样$拼接符才可以设置默认值 -->
        <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
    </properties>
 
    <!-- 2.全局配置参数 -->
    <settings>
        <!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 开启自动驼峰命名规则(camel case)映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启延迟加载开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载(即按需加载),默认值就是false -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 打开全局缓存开关(二级环境),默认值就是true -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
 
    <!-- 3.别名设置 -->
    <typeAliases>
        <typeAlias alias="user" type="com.pjb.mybatis.po.User"/>
        <typeAlias alias="teacher" type="com.pjb.mybatis.po.Teacher"/>
        <typeAlias alias="integer" type="java.lang.Integer"/>
    </typeAliases>
 
    <!-- 4.类型转换器 -->
    <typeHandlers>
        <!-- 一个简单的类型转换器 -->
        <typeHandler handler="com.pjb.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
 
    <!-- 5.对象工厂 -->
    <objectFactory type="com.pjb.mybatis.example.ExampleObjecFactory">
        <!-- 对象工厂注入参数 -->
        <property name="someProperty" value="100"/>
    </objectFactory>
 
    <!-- 6.插件 -->
    <plugins>
        <plugin interceptor="com.pjb.mybatis.example.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>
 
    <!-- 7.environments数据库环境配置 -->
    <!-- 和Spring整合后environments配置将被废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC事务管理 -->
            <transactionManager type="JDBC"/>
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="$jdbc.driver"/>
                <property name="url" value="$jdbc.url"/>
                <property name="username" value="$jdbc.username:root"/>
                <property name="password" value="$jdbc.password:123456"/>
            </dataSource>
        </environment>
    </environments>
 
    <!-- 8.加载映射文件 -->
    <mappers>
        <mapper resource="com.pjb.mybatis.sqlmap.UserMapper.xml"/>
        <mapper resource="com.pjb.mybatis.sqlmap.OtherMapper.xml"/>
    </mappers>
</configuration>

我们看一下parse函数的实现:

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

为了防止配置文件被重新解析,我们定义了一个全局的parsed属性,当解析之前,会通过parsed属性判断是否已经对config文件进行判断。如果已经解析,则抛出配置文件只能解析一次的异常;如果还没有被解析,则对配置文件进行解析。

同时我们可以看到,在进行配置文件解析的时候,是以configuration节点为起点的,而我们配置文件中,configuration也是根节点。在这里获取根节点,然后进行其他节点属性的获取。

private void parseConfiguration(XNode root) 
    try 
      // issue #117 read properties first
      // 加载属性节点中的属性子节点,并加载到配置中
      propertiesElement(root.evalNode("properties"));
        
      // 获取settings属性,并进行全局属性配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
        
      // 加载并配置typeAliases属性
      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"));
        
      // 加载并配置数据表mapper
      mapperElement(root.evalNode("mappers"));
     catch (Exception e) 
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    
  

通过parseConfiguration方法获取其相应的属性并进行相应的配置。我们看一下mapper的映射。

在mybatis配置文件xml中,对mapper的配置描述有四种写法:相对于类路径的资源引用,使用完全限定资源定位符,使用映射器接口实现类的完全限定类名和将包内的映射器接口实现全部注册为映射器。

<mappers>
  <!-- 使用相对于类路径的资源引用 -->
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    <!-- 使用完全限定资源定位符(URL) -->
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    <!-- 使用映射器接口实现类的完全限定类名 -->
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

我们看一下mybatis中是如何对这些mapper配置进行解析的,mapper属性配置参数页面中是使用XMLMappperBilder来进行工作的。

private void mapperElement(XNode parent) throws Exception 
    if (parent != null) 
      for (XNode child : parent.getChildren()) 
        if ("package".equals(child.getName())) 
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
         else 
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) 
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) 
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            
           else if (resource == null && url != null && mapperClass == null) 
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url))
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            
           else if (resource == null && url == null && mapperClass != null) 
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
           else 
          
        
      
    
  

这段代码比较明显,无论是哪一种指定mapper对应关系的方法,该解析都可以将其对应的数据进行解析并保存到配置中。

接着我们看一下,在对mappers中mapper解析后,如何对xml映射器进行解析的。在上述代码中,我们可以看出,该程序是通过mapperParse.parse()对XML映射文件进行解析的。在查看代码之前,我们先了解一下XML映射文件的几个顶级元素:

  • cache - 该命名空间的缓存配置
  • cache-ref - 引用其他命名空间的缓存配置
  • resultMap - 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素
  • parameterMap - 已被弃用
  • sql - 可被其他语句引用的可重用语句块
  • insert - 映射插入语句
  • update - 映射更新语句
  • delete - 映射删除语句
  • select - 映射查询语句

接着我们看一下解析代码:

public void parse() 
    if (!configuration.isResourceLoaded(resource)) 
		 // 加载所有的子标签
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
			// 绑定该命名空间下的mapper
      bindMapperForNamespace();
    

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  

// 对mapper.xml文件的解析
  private void configurationElement(XNode context) 

    // 首先判断根节点是否有namespace属性,该属性是必须要有的
    try 
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) 
        throw new BuilderException("Mapper's namespace cannot be empty");
      

      builderAssistant.setCurrentNamespace(namespace);

      // 获取对其他命名空间缓存配置的引用
      cacheRefElement(context.evalNode("cache-ref"));
      // 对本地命名空间缓存的配置
      cacheElement(context.evalNode("cache"));
      // 对mapper映射中参数的配置
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 描述从数据库查询后的结果集转换为java对象
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 获取其他语句引用的可重用语句块
      sqlElement(context.evalNodes("/mapper/sql"));
      // 获得MappedStatement对象(增删改查标签)
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
     catch (Exception e) 
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    
  

下面看一下如何解析增删改查标签并绑定statement。

// 获取MappedStatement对象(增删改查标签)
  private void buildStatementFromContext(List<XNode> list) 
    if (configuration.getDatabaseId() != null) 
      buildStatementFromContext(list, configuration.getDatabaseId());
    
    buildStatementFromContext(list, null);
  

  // 获取MappedStatement对象(增删改查标签)
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) 
    for (XNode context : list) 
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try 
        // 解析insert/delete/update/select标案
        statementParser.parseStatementNode();
       catch (IncompleteElementException e) 
        configuration.addIncompleteStatement(statementParser);
      
    
  

通过上述代码,可以看到,在进行增删改查解析的时候,mybatis使用XMLStatementBuilder进行解析,然后调用parseStatementNode()进行解析并绑定。

  public void parseStatementNode() 

    // 获取唯一ID
    String id = context.getStringAttribute("id");
    // 获取不同数据源的daaseId
    String databaseId = context.getStringAttribute("databaseId");

    // 如果获取的dataBaseId 与 需要的databasId 设置的一致
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) 
      return;
    

    String nodeName = context.getNode().getNodeNamemybatis源码过程学习梳理

mybatis源码过程学习梳理

mybatis学习 -每天一记(驼峰命名匹配)

《精品毕设》基于JAVA springboot+mybatis智慧生活学习分享平台(源码+sql+论文):主要实现登录注册首页信息浏览分类查看信息详情查看评论收藏浏览量关注以及后台管理

mybatis源码学习:基于动态代理实现查询全过程

mybatis专题-----mybatis源码学习