mybatis-执行流程

流程图:
在这里插入图片描述

1.读取资源,解析配置填充Configuration配置对象字段

资源的读取:由MyBatis的IO包完成的,其中重点职责的类为ClassLoaderWrapper以及Resources

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

解析配置:解析配置核心类之一是XMLConfigBuilder,解析入口是parse()方法

// 解析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);
    }
}

2.加载XXXMapper

映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,映射文件是在mybatis-config.xml中加载;可以加载多个映射文件。常见的配置的方式有两种,一种是package扫描包,一种是mapper找到配置文件的位置。

<mappers>
  <package name="org.mybatis.xxx"/>
</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.");
          }
        }
      }
    }
}

3.构造会话工厂获取SqlSessionFactory

这个过程其实是用建造者设计模式使用SqlSessionFactoryBuilder对象构建的,
读取解析配置文件并填充Configuration对象字段后,就创建DefaultSqlSessionFactory对象为SqlSessionFactory类型

SqlSessionFactory builder = new    SqlSessionFactoryBuilder().build(inputStream);
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);
}

4.创建会话对象SqlSession

SqlSession的创建使用了工厂方法模式,由DefaultSqlSessionFactory创建DefaultSqlSession对象为SqlSession类型

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

5.创建Executor执行器

Executor执行器是在创建DefaultSqlSession前一点进行创建的,因为SqlSession是高层接口层,它只是关心操作并不关心实现细节的东西的。
是MyBatis的核心,负责SQL语句的生成和查询缓存的维护,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护

  • SimpleExecutor – SIMPLE 就是普通的执行器。
  • ReuseExecutor-执行器会重用预处理语句(PreparedStatements)
  • BatchExecutor–它是批处理执行器
// 产生执行器
  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;
  }

6.获取Mapper

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

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的。

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代理类的

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

7.调用Mapper的方法

创建mapper代理对象,执行方法后会调用如下invoke()方法

 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;

}

8.封装结果集

可以封装成多种类型可以是基本数据类型,也可以是Map、List、POJO类型复杂数据类型。封装结果集的过程就和JDBC封装结果集是一样的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis-Plus(简称MP)是一个基于MyBatis的增强工具,它简化了MyBatis的开发流程,提供了更多的便捷功能和增强特性。下面是对MyBatis-Plus的介绍: 1. 代码生成器:MyBatis-Plus提供了一个代码生成器,可以根据数据库表结构自动生成实体类、Mapper接口以及XML映射文件,大大减少了手动编写这些代码的工作量。 2. 通用CRUD操作:MyBatis-Plus内置了通用的CRUD操作方法,包括插入、更新、删除和查询等,可以通过简单的方法调用完成数据库操作,无需编写SQL语句。 3. 条件构造器:MyBatis-Plus提供了强大的条件构造器,可以通过链式调用的方式构建复杂的查询条件,支持动态条件拼接和查询。 4. 分页查询:MyBatis-Plus支持分页查询,可以方便地进行分页操作,提供了多种分页插件和方法。 5. 逻辑删除:MyBatis-Plus支持逻辑删除功能,可以通过配置实现逻辑删除而不是物理删除数据。 6. 自动填充:MyBatis-Plus提供了自动填充功能,可以在插入或更新数据时自动填充指定字段的值,例如创建时间、更新时间等。 7. 性能分析:MyBatis-Plus内置了性能分析插件,可以方便地查看SQL执行时间、慢查询等信息,帮助优化数据库操作性能。 8. 多租户支持:MyBatis-Plus支持多租户的数据隔离,可以根据不同的租户ID自动切换数据源或者添加租户ID条件。 以上是对MyBatis-Plus的简要介绍,它是一个功能强大且易于使用的MyBatis增强工具,可以提高开发效率和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值