Mybatis执行流程源码分析


在这里插入图片描述
在这里插入图片描述

1、 创建SqlSessionFactory

SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(resourceAsStream);

  1. new 一个SqlSessionFactoryBuilder对象调用builder方法
  2. 创建一个XML解析器
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  3. 调用parser.parse()方法中的parseConfiguration(parser.evalNode("/configuration"))方法解析XML配置
  4. settingsElement(settings);解析每一个标签把详细信息保存在configuration中
  5. mapperElement(root.evalNode(“mappers”));解析Mapper.XML
    • 创建mapper解析器:XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    • 调用mapperParser.parse();中的configurationElement(parser.evalNode("/mapper")); 解析Mapper.XML的所有标签
    • buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));中的buildStatementFromContext(list, null);方法来解析增伤改查方法
    • 获得statementParser解析器调用statementParser.parseStatementNode();方法解析SQL语句获得所有标签内容
    • 调用addMappedStatement方法将每一个属性封装在一个MappedStatement中一个增删改查方法对应一个MappedStatement
    • 将MappedStatement也加到configuration中:configuration.addMappedStatement(statement);
  6. 解析完成后将封装了所有信息的全局Configuration返回:return configuration;
  7. 将configuration传入返回一个DefaultSqlSessionFactory:
    new DefaultSqlSessionFactory(config);

总结:把配置文件信息解析并保存在Configuration对象中,最后返回包含了configuration的DefaultSqlSessionFactory
在这里插入图片描述

  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.
      }
    }
  }
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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);
    }
  }
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

2、 获取SqlSession对象

SqlSession sqlSession = sqlSessionFactory.openSession();

  1. 调用openSession()方法返回openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);方法
  2. 获得一个Executorfinal:
    Executor executor = configuration.newExecutor(tx, execType);
    • 根据Executor在全局配置中的类型创建出SimpleExecutor或ReuseExecutor或BatchExecutor
    • 如果有二级缓存配置开启就会创建:
      executor = new CachingExecutor(executor);
    • 使用每一个拦截器(插件)包装每一个executor并返回
      executor = (Executor) interceptorChain.pluginAll(executor);
  3. 最后返回一个包含configuration和executor的DefaultSqlSession
    return new DefaultSqlSession(configuration, executor, autoCommit);
    在这里插入图片描述
  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);
      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();
    }
  }
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    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);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

3、 getMapper获取接口代理对象

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

  1. getMapper返回configuration.getMapper(type, this);又返回mapperRegistry.getMapper(type, sqlSession);(configuration中的mapperRegistry中的KnownMappers保存了所有Mapper的代理工厂)
  2. 获取MapperProxyFactory:
    MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
  3. 调用newInstance传入sqlSession:
    return mapperProxyFactory.newInstance(sqlSession);
  4. 创建mapperProxy,(实现InvocationHandler接口):
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
  5. 继续调用return newInstance(mapperProxy);
  6. 最终创建MapperProxy代理对象:
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

4、 执行Mapper的方法

User user = userMapper.getById(3L);

  1. MapperProxy执行invoke(Object proxy, Method method, Object[] args)方法
  2. 将方法包装成Mybatis能执行的mapperMethod,并执行execute方法:
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
    • command.getType()判断是增删改查的哪种方法(以查询为例子)
    • 通过convertArgsToSqlCommandParam获取参数:
      Object param = method.convertArgsToSqlCommandParam(args);
    • 继续调用参数名解析器的getNamedParams(args)方法(如果参数是一个直接返回,如果参数有多个则包装成map返回)
      paramNameResolver.getNamedParams(args);
    • 查询多个会调用result = executeForMany(sqlSession, args);方法
    • 查单个调用result = sqlSession.selectOne(command.getName(), param);方法(以查询单个为例子)
    • selectOne中调用this.selectList(statement, parameter);返回list的第一个
      • 先获取configuration中的MappedStatement(封装查询的详细信息)
        MappedStatement ms = configuration.getMappedStatement(statement);
      • 调用executor的查询方法
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      • wrapCollection(parameter)将参数包装,例如collection或者list等
      • 获得SQL语句参数等各种信息:
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        在这里插入图片描述
      • 继续调用query进行查询:
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      • 如果配置了缓存先从二级缓存里取,如果没有就利用executor进行查询:
        return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      • 继续从一级缓存里取值:
        list = resultHandler == null ? (List) localCache.getObject(key) : null;
      • 如果没有则是真正从数据库查询:并且查出放在一级缓存中
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      • 调用BaseExecutor的doQuery方法:
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
      • 创建StatementHandler 对象
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        • 根据配置的信息判断创建Statement或者PreparedStatement或者CallableStatement(默认PreparedStatement)
        • 创建Statement的构造函数里默认穿创建了:parameterHandler和resultSetHandler,并都是用拦截器包装。
      • 使用每一个拦截器包装statementHandler对象:
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      • 预编译SQL产生preparedStatement对象:
        stmt = prepareStatement(handler, ms.getStatementLog());
      • 调用ParamterHandler设置参数:
        handler.parameterize(stmt);
      • 又调用 TypeHandler给sql语句设置预编译参数
        typeHandler.setParameter(ps, i + 1, value, jdbcType);
      • 将处理好的Statement和resultHandler传入查询:
        handler.query(stmt, resultHandler);
      • 查出数据利用resultSetHandler处理结果(使用TypeHandler获取value值)
      • 关闭资源返回list

查询流程总结

在这里插入图片描述

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
    	Object param = method.convertArgsToSqlCommandParam(args);
        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()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        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;
  }
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  @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.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

5. 流程总结

  1. 根据配置文件(全局,sql映射)初始化出Configuration对象
  2. 创建一个DefaultSqlSession对象,它里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
  3. DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
    MapperProxy里面有(DefaultSqlSession);
  4. 执行增删改查方法:
    • 调用DefaultSqlSession的增删改查(Executor);
    • 会创建一个StatementHandler对象。同时也会创建出ParameterHandler和ResultSetHandler)
    • 调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数
    • 调用StatementHandler的增删改查方法;
    • ResultSetHandler封装结果

注意:四大对象(Executor、ParameterHandler、ResultSetHandler)每个创建的时候都有一个interceptorChain.pluginAll(xxx);和插件有关

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。了解mybatis运行原理才能更好开发插件。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值