mybatis(二):sql执行过程

下图中的3行代码基本就是mybatis的整个执行过程,下面我们来一步一步的看

Demo

1. 获取SqlSession

openSession方法在DefaultSqlSessionFactory中重载了多次,最终调用的是openSessionFromDataSource方法,构造一个DefaultSqlSession对象。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取环境对象
      final Environment environment = configuration.getEnvironment();
      // 根据环境对象获取事务工厂,如果没有配置则使用 ManagedTransactionFactory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 获取Transaction
      // 强大的spring,利用SpringManagedTransactionFactory生成的是 SpringManagedTransaction
      // 这里默认生成的是 ManagedTransaction
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 获取executor
      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();
    }
  }

newExecutor方法比较有意思,其他的都是根据配置实例化对象。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // executorType 默认配置为 ExecutorType.SIMPLE
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 获取批处理的executor
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    }
    // 获取可重用 Statement 的executor
    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;
  }

这里有一个mybatis plugin的使用

2.获取对应的mapper对象

接下来是获取对应的mapper类对象,也就是代理对象,继续看DefaultSqlSession的getMapper,直接调用的configuration.getMapper然后调用的是mapperRegistry.getMapper,所以我们直接看mapperRegistry.getMapper

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 {
      // <1>生成代理类
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

这里生成一个MapperProxy对象,调用这个对象的newInstance方法时会为对应的接口生成一个jdk的动态代理类。所以接下来的执行过程也就是看MapperProxy类的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      }
      // 如果是default类型方法
      // 利用 MethodHandles 执行 default方法
      else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 缓存method,或生成一个新的MapperMethod对象。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 执行
    return mapperMethod.execute(sqlSession, args);
  }

先看一下MapperMethod这个类,然后再接着看执行过程。

 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

SqlCommand和MethodSignature都是MapperMethod的内部类,SqlCommand有两个属性:name(类名.方法名),type(方法对应的xml标签类型,如:INSERT,UPDATE等)。MethodSignature保存的是方法对应的入参和出参类型。他们的构造方法如下:

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      // 根据类名.方法名获取对应的MappedStatement对象
      // 或者是递归查找父类,根据父类名.方法名获取
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        // 方法对应的xml标签类型
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 解析方法返回类型
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.returnsOptional = Optional.class.equals(this.returnType);
      // 获取 @mapKey 注解配置
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      // 获取 RowBounds 在方法入参中的 index
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      // 获取 ResultHandler 在方法入参中的 index
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 解析 @Param 注解,
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

接下来继续看mapperMethod的execute方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 不同的 sql 类型调用不同的方法
    switch (command.getType()) {
      case INSERT: {
        // 将方法的入参转换为具体 sql 的入参
        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);
          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;
  }

里面涉及到了所有的sql执行预期,我们挑两个来分析,1.sqlSession.update。2.executeForMany

3.执行

3.1executeForMany(一次查询多个结果)

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 根据@Param或者是默认命名param1,param2。集合所有参数
    Object param = method.convertArgsToSqlCommandParam(args);
    // 内存分页
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      <1>
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    }
    // 不分页
    else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    // 根据方法返回参数类型包装结果
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

接着看<1>处的代码

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 获取对应的MappedStatement,这里会再次解析,初始化时解析失败的mapper接口方法和xml中的标签
      MappedStatement ms = configuration.getMappedStatement(statement);
      // <1> 调用具体的executor执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

关于Executor接口,CachingExecutor当开启mybatis的二级缓存时使用,默认使用SimpleExecutor,因为一些方法在它的父类,BaseExecutor中,所以我们接下来进入BaseExecutor继续分析。

image
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // BoundSql用于存放sql和sql参数
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 一级缓存key
    // key的生成策略:id + offset + limit + sql + param value + environment id
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

继续看query方法

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.");
    }
    // 清除一级缓存
    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();
      // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

执行update,insert,delete,flushCache="true",commit,rollback,LocalCacheScope.STATEMENT等情况下,一级缓存就都会被清空,接下来继续看queryFromDatabase方法是如何查询数据库的

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 缓存占位
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 执行读操作
      // <1>
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 从缓存中,移除占位对象
      localCache.removeObject(key);
    }
    // 添加到缓存中
    localCache.putObject(key, list);
    // 暂时忽略,存储过程相关
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

<1>处的方法由BaseExecutor的子类实现。SimpleExecutor每次开始读或写操作,都创建对应的 Statement 对象,执行完成后,关闭该 Statement 对象。BatchExecutor批处理相关。ReuseExecutor* 每次开始读或写操作,优先从缓存中获取对应的 Statement 对象,如果不存在,才进行创建,执行完成后,不关闭该 Statement 对象,其它的,和 SimpleExecutor 是一致的。

重点看看SimpleExecutor,因为它是默认实现方式,后面的是关于sql的执行以及结果的映射。继续看simpleExecutor对doQuery方法的实现

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();
      // 默认使用PreparedStatementHandler
      // <1>
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 初始化StatementHandler对象,设置sql的参数
      // <2>
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

<1>处是创建新的StatementHandler,可以看到RoutingStatementHandler起始就是一个包装,正真工作的是delegate属性

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根据 StatementType 获取对应的 StatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      // 普通的
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
        // 预编译的
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
        // 存储过程相关
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

继续看<2>的代码

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获得 Connection 对象,如果是debug日志级别会多层 包装,创建代理类
    // 主要用于打印各种日志
    Connection connection = getConnection(statementLog);
    // 创建 Statement 或 PrepareStatement 对象
    // 并且如果需要返回主键,则使用 connection.prepareStatement 设置主键
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
    handler.parameterize(stmt);
    return stmt;
  }

继续看handler.query方法

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行查询
    ps.execute();
    // 处理返回结果
    return resultSetHandler.handleResultSets(ps);
  }

接下来是 resultSetHandler.handleResultSets 结果映射相关,我们在后文再介绍。

3.2 sqlSession.update(执行查询)

执行完查询后会调用rowCountResult对结果进行一个简单的处理然后返回结果,接下来分析下update方法的执行过程。

public int update(String statement, Object parameter) {
    try {
      // 标识是否发生数据变更
      dirty = true;
      // 获取 MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      // wrapCollection 用于包装入参
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("array", object);
      return map;
    }
    return object;
  }
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);
  }

doUpdate方法由baseExecutor方法的子类实现,我们继续看simpleExecutor对它的实现:

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());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

public int update(Statement statement) throws SQLException {
    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;
  }

此处是执行keyGenerator的processAfter方法,processBefore方法在各个statementHandler构造方法中调用,也就是configuration中的newStatementHandler方法调用RoutingStatementHandler的构造方法时调用。

参考资料:
Mybatis3.4.x技术内幕(二十一):参数设置、结果封装、级联查询、延迟加载原理分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容