面试高薪必备MyBatis执行流程

一、前言

    MyBatis至今被越来越多的公司青睐,其中的原因是它更加轻量级,可控性更高。许许多多的大中小型公司都喜欢在面试的时候提及MyBatis的原理或者MyBatis中一些重要的组件及实现。而与之增强版本MyBatis-Plus同样在面试上面被提问,懂得MyBatis的离理解MyBatis-Plus就不远了。两者差别在于对SQL的处理方式有所不同而产生了差异。

MyBatis有着不胜枚举的优良地方值得我们学习,在前面的一些文章中笔者也提及了,需要了解的童鞋可以访问主页自行学习,后续也会不断的推送关于MyBatis的解读文章。今天又放大招了。

二、MyBatis执行流程

(一)文章的主题思路

(二)读取资源,解析配置填充Configuration配置对象字段

这里涉及到MyBatis架构里面的基础层以及核心层。在前面的文章中笔者有所提及,感兴趣的读者可以回顾一下。解析配置填充Configuration配置对象字段的过程中使用了建造者模式。重要的事情讲三遍。解析配置填充Configuration配置对象字段的过程中使用了建造者模式解析配置填充Configuration配置对象字段的过程中使用了建造者模式。看文章一定要反复读而不是一读完就行了,顺着来也会逆着来,抓住重点词汇,包括开发需求也一样的道理。Configuration配置对象就是一个配置数据中心,用于存取数据,就像未经持久化的数据库。关于设计模式,也出了几篇文章,后面还会继续。

1、读取(配置)资源

资源的读取是由MyBatis的IO包完成的,其中重点职责的类为ClassLoaderWrapper以及Resources,关于它们笔者在前面的文章(MyBatis是如何加载配置文件的2)介绍了,这里就不分析了。

读取资源代码入口:

  public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }

  public static InputStream getResourceAsStream(ClassLoader loader,
                                                String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
  }

2、解析配置

解析配置核心类之一是XMLConfigBuilder,解析入口是parse()方法。主要解析mybatis-config.xml(配置文件的样例如笔者前面写的文章MyBatis核心武器的源泉),解析mappers封装到了mapperElement(root.evalNode("mappers"));方法中

  // 解析mybatis-config.xml的入口方法
  public Configuration parse() {
    // 如果已经解析过了,报错
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true; // 标志已解析
    // 在mybatis-config.xml配置文件中查找<configuration>节点,并解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  // 解析配置节点
  private void parseConfiguration(XNode root) {
    try {
      // 分步骤解析
      //issue #117 read properties first
      // 1.properties
      propertiesElement(root.evalNode("properties"));
      // 2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 3.插件
      pluginElement(root.evalNode("plugins"));
      // 4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      // 5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      // 7.环境
      environmentsElement(root.evalNode("environments"));
      // 8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

MyBatis把每个节点的解析都封装成了一个方法,这样职责清晰,方便维护与阅读。

3、解析XXXMapper.xml配置

// 10.映射器
//	10.1使用类路径
//	<mappers>
//	  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
//	  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
//	  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
//	</mappers>
//
//	10.2使用绝对url路径
//	<mappers>
//	  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
//	  <mapper url="file:///var/mappers/BlogMapper.xml"/>
//	  <mapper url="file:///var/mappers/PostMapper.xml"/>
//	</mappers>
//
//	10.3使用java类名
//	<mappers>
//	  <mapper class="org.mybatis.builder.AuthorMapper"/>
//	  <mapper class="org.mybatis.builder.BlogMapper"/>
//	  <mapper class="org.mybatis.builder.PostMapper"/>
//	</mappers>
//
//	10.4自动扫描包下所有映射器
//	<mappers>
//	  <package name="org.mybatis.builder"/>
//	</mappers>
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 10.4自动扫描包下所有映射器
          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) {
            // 10.1使用类路径
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 映射器比较复杂,调用XMLMapperBuilder
            // 注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // 10.2使用绝对url路径
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 映射器比较复杂,调用XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // 10.3使用java类名
            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.");
          }
        }
      }
    }
  }

(三)创建SqlSessionFactory

SqlSessionFactory的创建使用了建造者模式,由SqlSessionFactoryBuilder创建的。

读取解析配置文件并填充Configuration对象字段后,就创建DefaultSqlSessionFactory对象为SqlSessionFactory类型。

  /**
   * 第8种方法和第4种方法差不多,Reader换成了InputStream
   * @param inputStream 输入数据流
   * @param environment 数据库环境
   * @param properties 属性
   * @return 工厂
   */
  public SqlSessionFactory build(InputStream inputStream,
                                 String environment,
                                 Properties properties) {
    try {
      // 读取资源,解析配置并填充Configuration
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return this.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.
      }
    }
  }

  /**
   * 最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
   * @param config 配置
   * @return 工厂
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

(四)创建SqlSession会话

SqlSession的创建使用了工厂方法模式,由DefaultSqlSessionFactory创建DefaultSqlSession对象为SqlSession类型。创建会话的方法openSession()有多个重载,最终委派给下面两个方法去处理创建DefaultSqlSession

  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);
      // 生成一个执行器(事务包含在执行器里)
      final Executor executor = configuration.newExecutor(tx, execType);
      // 然后产生一个DefaultSqlSession
      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();
    }
  }

  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }      
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

(五)创建Transaction事务

从上面创建会话过程中可以看出Transaction是在创建DefaultSqlSession前一点进行创建的,它是由工厂方法模式进行创建的,由TransactionFactory的newTransaction()方法创建的。一种是JdbcTransaction,另一种是ManagedTransaction。但是实际中是由另一个框架管理的。

(六)创建Executor执行器

从上面创建会话过程中可以看出Executor执行器是在创建DefaultSqlSession前一点进行创建的,因为SqlSession是高层接口层,它只是关心操作并不关心实现细节的东西的。

  // 产生执行器
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    // 这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // 如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 此处调用插件,通过插件可以改变Executor行为
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

(七)获取Mapper

创建会话后,通过会话对象获取mapper。其中会委派给Configuration对象获取。

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

Configuration又会委派给映射器注册机MapperRegistry去获取。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

MapperRegistry中通过MapperProxyFactory创建代理mapper的。

  @SuppressWarnings("unchecked")
  // 返回代理类
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取mapper代理工厂
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // 在map中找不到则表示没有将mapper类注册进来,抛出BindingException
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 使用工厂进行创建一个实例,本质上是通过代理模式创建了一个代理类,创建过程中出现异常抛出BindingException
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory最终交由JDK动态代理类Proxy创建mapper代理类的。

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader()
      , new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession
      , mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

(八)调用Mapper的方法

创建mapper代理对象,执行方法后会调用如下invoke()方法,整体以插入数据为例

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

接下来会执行到MapperMethod的execute()方法

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        // 对用户传入的参数进行转换,下同
        Object param = method.convertArgsToSqlCommandParam(args);
        // 执行插入操作,rowCountResult 方法用于处理返回值
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        // 根据目标方法的返回类型进行相应的查询操作
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          // 执行查询操作,并返回多个结果
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          // 执行查询操作,并将结果封装在 Map 中返回
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          // 执行查询操作,并返回一个 Cursor 对象
          result = executeForCursor(sqlSession, args);
        } else {
          // 执行查询操作,并返回一个结果
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        // 执行刷新操作
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method " +
        "with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

你会发现执行的还是sqlSession的insert()方法

(九)执行DefaultSqlSession的insert()方法

执行DefaultSqlSession的insert()方法,最终到如下的update()方法。委托Exexutor继续执行。

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

(十)执行Exexutor的update()方法

来到BaseExecutor的update()方法

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance()
      .resource(ms.getResource())
      .activity("executing an update")
      .object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 先清局部缓存,再更新,如何更新交由子类,模板方法模式
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

(十一)执行SimpleExecutor的doUpdate()方法

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      // 获得配置文件对象
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration
        .newStatementHandler(this, ms, parameter
          , RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 最终是一个statement进行处理
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

(十二)执行PreparedStatementHandler的update()方法

  @Override
  public int update(Statement statement) throws SQLException {
    //调用PreparedStatement.execute和PreparedStatement.getUpdateCount
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

底层用的就是PreparedStatement的execute()方法了,来到这里是不是感到很亲切,原来绕了一大圈最终还是要用到底层的东西。

MyBatis的其他查询、修改、删除执行流程都是这个走向,感兴趣的读者可以把它收藏起来仔细研究研究。笔者还有其他关于MyBatis的文章,可以关注一下不迷路,在主页就可以查看哦!后续也会陆陆续续出一些关于MyBatis的文章。最后说一下的是,程序员这条路,最好是有一个引路人。

如果这篇文章对你有所帮助,点点赞吧

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡布奇诺-海晨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值