四、MyBatis中查询执行流程

一、查询执行大致流程

在MyBatis中,查询执行的大致流程如下:

对应的时序图如下:

二、MapperProxy绑定MappedStatement

MyBatis Mapper Bean初始化深度解析中说过,mapper bean就是MapperProxy通过jdk动态代理实现的,所以,执行mapper bean中的方法时,就是执行MapperProxy中的invoke方法。执行查询时,其debug信息如下:

在org.apache.ibatis.binding.MapperProxy#cachedMapperMethod方法中,会判断是否缓存了方法对应的MapperMethod,如果缓存了,就直接返回缓存的MapperMethod。MapperMethod中的SqlCommand就是需要执行的sql语句的封装,在SqlCommand会根据statementId(mapper的interfacename+"."+methodname)去查找对应的MappedStatement,然后返回这个查找到的MappedStatement。代码如下:

   public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      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();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
    
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }

这里的statementId就是interfacename+"."+methodname,也是mapper中的namespace+"."+id。

三、SimpleExecutor设置查询参数与缓存设置

1、查询参数设置

跟随代码一步一步debug,会到达SimpleExecutor的query方法(其实是父类BaseExecutor的query方法),其代码如下:

 @SuppressWarnings("unchecked")
  @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.");
    }
    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;
  }

这里的queryFromDatabase方法会调用doQuery方法,其代码实现为:

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

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

这里会获取connection,并且会获取statement,从connection中获取statement的代码如下:

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

可以看出,这里获取的是PrepareStatement,这样可以防止sql注入。在handler.parameterize(stmt)中,会调用typeHandler设置查询参数,具体如下:

这样以后,查询语句就变成如下sql了:

SELECT * FROM CNT_USER WHERE name ='zhangsan'

2、缓存设置

查询结果返回后,会将查询结果保存在一级缓存中,以便同一个会话中的后续相同查询使用。

缓存key的生产规则:

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

缓存key有MappedStatement的id、查询sql、查询参数、环境等组成。样例值如下:

461375566:-715040544:com.iwill.mybatis.dao.mapper.ext.UserMapperExt.findUserListByName:0:2147483647:SELECT * FROM CNT_USER WHERE name =?:zhangsan:SqlSessionFactoryBean

缓存查询:

将查询结果放入缓存中:

四、PreparedStatementHandler执行查询

在方法org.apache.ibatis.executor.statement.PreparedStatementHandler#query中,会调用PreparedStatement的execute方法来执行查询。

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

五、DefaultResultSetHandler进行查询结果处理

在查询结果返回时,会将SQL查询的结果映射到resultMap制定的类型。例如findUserListByName:

    <resultMap id="BaseResultMap" type="com.iwill.mybatis.dao.model.UserDTO">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="age" jdbcType="INTEGER" property="age" />
    </resultMap>

    <select id="findUserListByName" parameterType="java.lang.String" resultMap="BaseResultMap">
        SELECT * FROM CNT_USER WHERE name =#{name}
    </select>

这里的返回类型就是UserDTO,查询结果会映射到UserDTO,查询结果如下:

映射时,使用java的反射机制来将对应的值设置到对应的属性中。具体的代码如下:

查询的整个流程就是这样的了,里面涉及很多细节:一级缓存、二级缓存、链接管理等,值得深入学习。

转载于:https://my.oschina.net/yangjianzhou/blog/3026822

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值