第四篇 Mybatis源码阅读:Mybatis执行流程

一、Mybatis的执行流程

  Mybatis做一个ORM框架,最主要的目的就是完成数据的持久化操作,实现Java对象和数据的映射,所以这个过程中,必然会涉及到连接数据库、执行SQL、结果封装成Java对象这些操作,Mybatis的执行流程也会伴随这些操作进行,首先,通过下面这张图了解Mybatis执行的大致流程,后面从源码级别去跟进Mybatis的执行。

 

  Mybatis从加载配置文件开始初始化全局配置组件Configuration,配置文件包括全局配置XML和Mapper.XML,全局配置XML如下,其主要内容包括了数据源、日志、缓存等配置,全局配置XML和全局配置组件Configuration是对应的。之后,通过SqlSessionFactory创建会话SqlSession,SqlSession本身不会执行SQL,委托给Executor执行器处理,Executor完成数据库操作。

<?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="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
    </properties>
    <settings>
      <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    <plugins>
      <plugin interceptor=""></plugin>
    </plugins>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.117.128:3306/pattern?characterEncoding=UTF8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="com.mybatis.demo.mapper.BlogMapper"/>
    </mappers>
</configuration>

二、源码阅读

  Mybatis的源码非常简洁,并且命名也通俗易懂,很适合作为源码阅读的第一课,首先Mybatis会使用SqlSessionFactoryBuilder的build方法去创建SqlSessionFactory,创建SqlSessionFactory的过程中,需要解析XML,数据源配置等,所以使用了构建者模式(构建者模式一般用于复杂对象的创建),构建代码如下。

// SqlSessionFactoryBuilder类
  /*
    构建SqlSessionFactory
   **/
  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文件,并解析XML成XPathParser对象,之后调用parse()方法,并交给parseConfiguration()方法处理,在parseConfiguration方法中会获取</configuration>标签的所有内容,并逐步注释各个配置到Configuration对象中。这块儿的解析顺序是取决和XML配置的顺序,这个由http://mybatis.org/dtd/mybatis-3-config.dtd约束。

// XMLConfigBuilder类

  //初始化configuration配置
  private void parseConfiguration(XNode root) {
    try {
      // XML文件按下面顺序解析,所以XML配置也会按照此顺序
      // 解析外部属性配置<>,如<properties><property name="username" value="dev_user"/></properties>
      // 这些属性会被放入到configuration的Variables中管理,Variables是一个Hashtable
      propertiesElement(root.evalNode("properties"));
      // 解析setting调整配置,如<settings><setting name="lazyLoadingEnabled" value="true"/></settings>
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //日志配置
      loadCustomLogImpl(settings);
      //解析类型别名,如<typeAliases><typeAlias alias="Author" type="domain.blog.Author"/></typeAliases>
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析插件 <plugins><plugins>
      pluginElement(root.evalNode("plugins"));
      //对象工厂标签<objectFactory/>
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //环境配置<environment/>,包括数据源和事务管理
      environmentsElement(root.evalNode("environments"));
      //数据库厂商标识<databaseIdProvider/>, 用于适配多数据库
      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);
    }
  }

这里需要说一下mapperElement方法,在这个方法中,遍历每一个Mapper.xml,将它们解析放入Configuration的MapperRegistry之中。在这里有两个大分支分别处理<package/>和<mapper/>,其中<mapper/>又有三个小分支,他们的含义如下:
1、<package name="org.mybatis.builder"/> 将包内的映射器接口实现全部注册为映射器
2、<mapper resource="org/mybatis/builder/AuthorMapper.xml"/> 使用相对于类路径的资源引用
3、<mapper url="file:///var/mappers/AuthorMapper.xml"/> 使用完全限定资源定位符(URL)
4、<mapper class="org.mybatis.builder.AuthorMapper"/> 使用映射器接口实现类的完全限定类名

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // <package name="org.mybatis.builder"/
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          //直接添加包名,通过遍历包下面的接口添加到MapperRegistry中
          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) {
            // <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
            ErrorContext.instance().resource(resource);
            try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              //解析xml的namespace的值得到接口添加到MapperRegistry中
              mapperParser.parse();
            }
          } else if (resource == null && url != null && mapperClass == null) {
            // <mapper url="file:///var/mappers/AuthorMapper.xml"/> 使用完全限定资源定位符(URL)
            ErrorContext.instance().resource(url);
            try (InputStream inputStream = Resources.getUrlAsStream(url)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            //解析xml的namespace的值得到接口添加到MapperRegistry中
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            // <mapper class="com.mybatis.demo.mapper.BlogMapper"/>
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 直接添加接口
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

  在mapper分支中,url,resource和class只能存在其一,否则抛出异常BuilderException,通过跟进addMapper方法,可以发现,这些Mapper信息最终都存在MapperRegistry的knownMappers中,MapperRegistry又归属configuration对象持有,所有最后的结果都是注册mapper信息到configuration,到下面是MapperRegistry的源码,在MapperRegistry中一个HashMap对象knownMappers存储MapperProxyFactory,MapperProxyFactory根据mapper接口信息创建代理类,创建逻辑在getMapper方法中,代理类中会持有一个SqlSession对象。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  //获取代理对象
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //实例化,注入sqlSession对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
  // 添加Mapper信息
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        //判断是否重复,重复抛出异常
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
  ...
}

  到这里Mybatis的初始化阶段就结束了,之后就是构建SqlSession执行SQL了。sqlSessionFactory首先会调用openSession()方法,再交给openSessionFromDataSource方法处理。方法有三个参数,分别是执行器Executor的类型,事务隔离级别TransactionIsolationLevel,事务默认提交。Executor的类型有三种,分别SIMPLE, REUSE, BATCH,事务隔离级别一般是有四种,但是TransactionIsolationLevel类中多了一种不支持事务的NONE,openSessionFromDataSource最后创建了一个DefaultSqlSession返回,源码如下:

// DefaultSqlSessionFactory类

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //开启一个事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //DefaultSqlSession持有一个Executor对象,所以先创建一个执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建一个默认的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

//执行器枚举类ExecutorType和事务隔离枚举类TransactionIsolationLevel

public enum ExecutorType {
  SIMPLE, //简单执行器
  REUSE,  //可重用执行器
  BATCH   //批处理执行器
}
public enum TransactionIsolationLevel {
  NONE(Connection.TRANSACTION_NONE),   //不支持
  READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),  // 读提交
  READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED), // 读未提交
  REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ), // 可重复读
  SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE),  //可串行化
  /**
   * A non-standard isolation level for Microsoft SQL Server.
   * Defined in the SQL Server JDBC driver {@link com.microsoft.sqlserver.jdbc.ISQLServerConnection}
   *
   * @since 3.5.6
   */
  SQL_SERVER_SNAPSHOT(0x1000);

  private final int level;

  TransactionIsolationLevel(int level) {
    this.level = level;
  }

  public int getLevel() {
    return level;
  }
}

SqlSession创建完成之后,就是获取代理类了,就是SqlSession的getMapper方法,这个方法最终进入到MapperRegistry的getMapper方法,mapperProxyFactory会newInstance代理类返回,在这个代理类中会持有一个sqlSession对象,代理类表面是执行Mapper接口的方法,实际上是调用持有的sqlSession对象,sqlSession对象对象中持有一个Executor执行器,Executor是一个接口,UML类图如下

     

这里使用了装饰器模式,在执行器的单独篇章中讲,这里以一个select查询为例,sqlSession对象首先会使用缓存执行器CachingExecutor执行器的query方法,在这个方法中,首先会判断二级缓存是否开启,开启,则处理缓存逻辑,反之,进入到BaseExecutor的query方法处理,在这个方法里,会处理一级缓存,如果一级缓存没有命中,就会进入调用doQuery方法,在baseExecutor中doQuery是一个抽象方法,交由子类实现,这种类似于模板方法模式设计,BaseExecutor有三个实现类,这里默认使用SimpleExecutor,进入SimpleExecutor的doQuery方法,这里逻辑就跟jdbc非常相似了,首先会创建prepareStatement,执行SQL后,将结果交于ResultHandler处理返回。

// CachingExecutor类

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) { //二级缓存处理逻辑
      flushCacheIfRequired(ms); // 清空二级缓存
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //获取二级缓存
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          //缓存为null,委托给BaseExecutor执行查询
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //未开启二级缓存逻辑
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

// baseExecutor类

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //如果查询堆栈是0(就是之前没有执行过)且Mapper的flushCacheRequired是true
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //一级缓存中获取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //缓存为空,从数据库获取
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

// SimpleExecutor类

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

下面用一张图总结整个流程:

posted @ 2019-07-26 14:14  哲雪君!  阅读(452)  评论(0编辑  收藏  举报