0
点赞
收藏
分享

微信扫一扫

Mybatis源码剖析(1)之Mybatis的执行流程

自由情感小屋 2022-05-05 阅读 124
java

分析开始:

1.通过Resources加载mybatis-config.xml配置文件,将配置文件解析为一个输入流。

Reader reader =  Resources.getResourceAsReader("mybatis-config.xml");
public static Reader getResourceAsReader(String resource) throws IOException {
  Reader reader;
  if (charset == null) {
    reader = new InputStreamReader(getResourceAsStream(resource));
  } else {
    reader = new InputStreamReader(getResourceAsStream(resource), charset);
  }
  return reader;
}

2.创建sqlSessionFactoryBuilder对象,调用它的build方法去创建一个sqlSessionFactory对象。

2.1 在它的build方法中,会先去解析配置文件,获取配置文件中根节点<configuration>下的不同的标签中的属性信息,并将获取到的内容封装在Configuration配置类中:

private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
      // 如果我们开启了二级缓存,在这里可以获取到cacheEnabled->true
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // 解析环境信息,创建事务工厂和数据源对象
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析<mappers>标签下注册的全部mapper.xml文件
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

比如:①可以获取到<environments>标签下的<transactionManager>和<dataSource>的信息,并创建指定类型的事务工厂和DataSource数据源对象,将其封装成Environment对象,放入Configuration配置类中

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
   // 获取<transactionManager>标签中配置的事务管理器的类型信息,并创建相应类型的事务工厂
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
  // 获取<dataSource>标签中的type属性的值,创建指定类型的数据源工厂,并向工厂中设置数据源的配置信息
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
      // 创建数据源对象
        DataSource dataSource = dsFactory.getDataSource();
      // 封装事务工厂和数据源对象为环境对象
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
 // 将环境对象放入configuration配置文件对应的配置类中      
          configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

②还可以获取到<mappers>标签下,注册的全部的mapper.xml配置文件信息,根据指定的resource路径,加载相应的mapper配置文件,并解析配置文件:

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
      // 循环遍历每一个注册的<mapper>标签
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        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) {
          ErrorContext.instance().resource(resource);
    // 获取配置文件的输入流
          InputStream inputStream = Resources.getResourceAsStream(resource);
      // 创建mapper配置文件解析器
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
       // 解析配置文件
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
        .................
        } else if (resource == null && url == null && mapperClass != null) {
        .................
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}
public void parse() {
    // 判断资源是否已经加载进来了,eg:/mapper/StudentMapper.xml
  if (!configuration.isResourceLoaded(resource)) {
    // a  parser.evalNode("/mapper")能够拿到配置文件中<mapper>标签对象,configurationElement()解析配置文件中的内容
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    // 获取命名空间对应的mapper接口,解析接口中的方法
    bindMapperForNamespace();
  }
​
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}   

a.获取nameSpace命名空间,解析配置文件中各种类型的标签比如:<cache>、<ResultMap>、<ParameterMap>、<KeyGenerator>,将其封装成对象放入Configuration中对应的Map集合里;

还会遍历,解析SQL语句标签<select>、<update>、<delete>、<insert>,获取标签的属性信息,比如id,parameterType,useCache等,并且会在此处将sql语句解析成sqlSource对象,在解析成为sqlSource对象的过程中,也就完成了用替换#{}(如果是${},在此处不会发生任何替换,得到的sql语句仍然带有${xxx},实际上这里,使用#{}和${}会创建不同的sqlSource对象,如果使用了#{}会生成RawSqlSource对象,使用${}会生成DynamicSqlSource对象,怎么判断应该生成什么类型的对象呢?根据isDynamic标志进行判断的,默认是false生成RawSqlSource对象,如果节点是一些特定的类型就会设置isDynamic=true,具体什么类型的节点感兴趣的可以深入研究一下),最后将这些属性信息都封装到MappedStatement对象中,mappedStatement对象构建完成后,将nameSpace+id(方法名)作为key,MappedStatement对象作为value放到configuration对象的mappedStatements集合中(这里实际上还会仅以id作为key,mappedStatement对象作为value也添加到集合中,也就是一条sql语句在mappedStatements集合中,有两个key-value与其对应,value相同都是mappedstatement,key不同,一个是nameSpace+id,一个是id)

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"));
 //  解析SQL类型的标签<select><delete><update><insert>,创建每个sql语句相对应的mapperStatement  
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
 // 遍历全部的SQL语句标签
  for (XNode context : list) {
     // 创建每个sql语句对应的statementbuilder构建器
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
        // 解析创建相对应的mapperstatement对象.....
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

b.加载当前配置文件中nameSpace命名空间对应的接口类对象,并创建该接口对应的MapperProxyFactory对象,将接口的Class对象作为key,MapperProxyFactorymapper代理工厂作为value放到MapperRegistry对象的knownMappers集合中。

2.2 配置文件解析完成,获得了配置类,创建DefaultSqlSessionFactory对象,并传入配置类,于是获得了sqlSession工厂对象

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

这里使用了构建者模式,因为创建sqlSessionFactory对象,需要传入一个配置类,而配置类又需要通过解析配置文件获得,配置文件的解析过程又很复杂,所以希望在用户不知道构建过程和细节的情况下就可以直接创建复杂的对象,所以使用了sqlSessionFactoryBuilder来帮我们完成配置文件的解析,以及配置类的生成,对用户来说就隐藏了复杂的构建过程,就可以直接获得sqlSessionFactory对象。

3.调用工厂的openSession()方法,获取sqlSession对象,内部又调用了openSessionFromDataSource()方法,在此方法中:

3.1 使用事务工厂创建了一个相应类型的事务对象,事务对象中封装了数据源对象,事务的传播行为以及是否自动提交

3.2 根据执行器的类型创建对应的执行器对象(默认是SIMPLE类型的,执行器是来帮我们执行sql语句的,可以将其理解为JDBC中的statement),执行器对象中封装了配置类对象以及事务对象,如果开启了缓存(默认是开启的),会创建缓存执行器对象,并将前面创建的执行器对象封装在缓存执行器对象中。

3.3 创建sqlSession对象,将配置类、执行器、以及是否自动提交标志都封装在sqlSession对象中

public Configuration getConfiguration() {
  return configuration;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
      // 获取前面封装好的环境对象
    final Environment environment = configuration.getEnvironment();
      // 获取事务工厂
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 3.1 创建事务对象
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 3.2 创建执行器对象
    final Executor executor = configuration.newExecutor(tx, execType);
      // 3.3 创建sqlSession对象
    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();
  }
}

4.当我们调用sqlSession对象的getMapper()方法时,最终会调用MapperRegistry对象的getMapper()方法,在此方法中:

从集合knownMappers中获取到指定类型的MapperProxyFactory对象,利用代理工厂创建mapper接口代理对象,这里使用的是JDK动态代理,传入的InvocationHandlerMapperProxy对象,该对象内部封装了sqlSession、mapper接口类对象、接口中方法缓存,那当我们调用代理对象的方法时,就会来到MapperProxy对象的invoke()方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取指定类型的mapper代理工厂对象,
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // 获取不到会报错,因为前面已经在解析命名空间对应的接口时,创建完成了,并放入到了 knownMappers集合中
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
      // 创建mapper代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
public T newInstance(SqlSession sqlSession) {
    // 创建MapperProxy对象,实现了InvocationHandler接口
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
 // 使用JDK动态代理创建mapper接口的代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

5.调用代理对象的指定方法并传入参数,完成对数据库的增删改查操作,方法会被拦截,来到MapperProxy对象的invoke方法,在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 if (method.isDefault()) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
    // 5.1 创建mapperMethod对象并将其缓存起来
  final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 5.2 执行方法对应的sql语句,返回操作数据库的结果
  return mapperMethod.execute(sqlSession, args);
}

5.1 创建MapperMethod对象,MapperMethod对象中封装了SqlCommandMethodSignature对象

private MapperMethod cachedMapperMethod(Method method) {
    // 创建MapperMethod对象,method作为key,MapperMethod作为value,放入methodCache缓存中
  return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 创建sqlCommand对象
  this.command = new SqlCommand(config, mapperInterface, method);
    // 创建MethodSignature对象
  this.method = new MethodSignature(config, mapperInterface, method);
}

创建sqlCommand对象时:会拼接nameSpace和方法名作为key,去获取我们前面创建的MappedStatement对象,从MappedStatement对象中获取当前调用的方法对应于xml文件中SQL标签的id,以及sql命令的类型,将其赋值给sqlCommand的相应属性。

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 {
   // 从mappedStatement中,获取SQL标签(<select>、<insert>..)的id,赋值给SqlCommand的name属性
    name = ms.getId();
   // 获取sql命令的类型,赋值给type属性
    type = ms.getSqlCommandType();
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

创建MethodSignature对象时:会获取方法的返回值类型、创建方法参数名称解析器对象(在参数名称解析器中会去获取方法参数类型、方法参数中的注解,注解的值等信息,保存起来),并将它们封装在该对象中。

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);
  this.mapKey = getMapKey(method);
  this.returnsMap = this.mapKey != null;
  this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
  this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
    // 创建参数名称解析器,在创建的过程中会获取方法的参数类型信息,以及方法的参数注解信息
  this.paramNameResolver = new ParamNameResolver(configuration, method);
}

然后将当前调用的Method对象作为key,创建的MapperMethod对象作为value放入MapperProxy的methodCache集合中。

5.2 调用mapperMethod对象的execute方法,完成相应sql语句的执行,在此方法中:

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
     // insert类型的命令
    case INSERT: {
     // 获取参数值
      Object param = method.convertArgsToSqlCommandParam(args);
      // 调用sqlSession的insert方法
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
   // update类型的命令
    case UPDATE: {
   // 获取参数值
      Object param = method.convertArgsToSqlCommandParam(args);
     // 调用sqlSession的update方法
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
  // delete类型的命令
    case DELETE: {
   // 获取参数值
      Object param = method.convertArgsToSqlCommandParam(args);
     // 调用sqlSession的delete方法
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
  // select类型的命令
    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);
       // // 调用sqlSession的selectOne方法
        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命令的类型,不同的类型,调用sqlSession的不同方法(比如:增加操作,调用sqlSession的insert方法;删除操作调用sqlSession的delete方法等),但无论是哪种类型都需要先调用mapperMethod对象的convertArgsToSqlCommandParam()方法,获取参数名称及其对应的参数值(如果只有一个参数并且没有使用参数注解直接返回参数值;如果有多个参数或者参数有注解,返回参数名-参数值对象的Map集合)

②调用sqlSession的相应方法,执行对数据库的操作,以selectOne方法为例:

a.获取方法对应的mappedStatement对象,调用执行器(DefaultSqlSession中的CachingExecutor)的query()方法进行查询操作

public <T> T selectOne(String statement, Object parameter) {
  // 进行相应的查询操作
  List<T> list = this.selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}    
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 获取方法对应的mappedStatement对象
    MappedStatement ms = configuration.getMappedStatement(statement);
   // 调用执行器的query()方法进行查询操作,
    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();
  }
}

b.在query()方法中:先获取BoundSql对象,BoundSql对象中封装了配置类对象、sql语句、sql语句中的参数和方法中的参数映射对象列表、参数值对象,这里注意:如果使用了#{},那么得到的sql语句中的变量位置仍是占位符,如果使用${},那么得到的sql语句就已将帮我们完成了参数的值的填充。因为内部会调用sqlSourcegetBoundSql()方法,不同的sqlSource,getBoundSql()方法的实现必然不同,前面已经提过了,使用#{}会创建RawSqlSource对象,使用${}会创建DynamicSqlSource对象,在DynamicSqlSource对象的getBoundSql()方法中,就会完成对参数值的填充

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql对象
  BoundSql boundSql = ms.getBoundSql(parameterObject);
   // 创建缓存键对象,用作缓存执行结果时的key,缓存键中包含nameSpace+id(方法名),sql(查询语句),环境信息(development,test.....)
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 进行查询操作
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public BoundSql getBoundSql(Object parameterObject) {
  // 创建BoundSql对象
  return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

调用执行器(DefaultSqlSession中的CachingExecutor)的query()方法进行查询操作,在此方法中会先从MappedStatement中获取缓存对象,这里获取到的是二级缓存,二级缓存是与mapper.xml文件绑定的,也就是说每个xml文件(mapper接口)对应一个的二级缓存,对于每条sql语句在他们的mappedStatement中都有一个属性Cache,保存当前mapper接口(xml文件)对应的二级缓存,这个属性是在解析mapper.xml文件的时候进行赋值的,如果某个sql语句使用useCache=false关闭了二级缓存(默认是开启的),在该sql语句的ms中仍会给cache属性赋值的,只不过会先判断useCache是否为true,才会从二级缓存中获取。

先尝试从二级缓存中获取,方法的执行结果;如果没有开启二级缓存直接调用 delegate(CachingExecutor中的Executor执行器,实际上是我们之前根据执行器类型,创建的执行器对象)的query()方法

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
 // 从mappedStatement中获取缓存对象(二级缓存)
  Cache cache = ms.getCache();
  // 如果开启二级缓存,尝试从二级缓存中获取方法的执行结果,没有获取到再执行查询操作
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  // 没有开启二级缓存直接调用执行器的query()方法
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

在BaseExcutor执行器的query()方法中:

会先尝试从本地缓存(一级缓存)中获取方法的执行结果,一级缓存是PerpetualCache对象,它有两个属性,一个是id="LocalCache",另一个是Map集合,key为缓存键cacheKey,value为执行结果对象。本地缓存是BaseExcutor执行器的属性,在创建执行器对象时,调用父类BaseExcutor的空参构造器,就会创建本地缓存对象,并设置id为"LocalCache"。前面分析过,当我们创建一个sqlSession对象的时候,就会创建一个执行器对象,将其作为sqlSession对象的executor属性保存,所以一级缓存是sqlSession级别的。每开启一个sqlSession都会创建一个一级缓存。

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()方法执行查询操作,获取结果集,将查询到的结果放入本地缓存(一级缓存),并返回执行结果。

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 {
      // 进行查询操作
    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;
}

在doQuery()方法中会先调用prepareStatement()方法获取Statement对象,然后再调用statement对象的execute()方法执行查询操作,获取结果集,最后关闭statement。

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);
     // 获取statement对象
    stmt = prepareStatement(handler, ms.getStatementLog());
      // 真正的执行sql语句,调用preparedStatement的execute方法
    return handler.query(stmt, resultHandler);
  } finally {
      // 关闭statement
    closeStatement(stmt);
  }
}

prepareStatement()方法中,会先获取Connection连接对象,然后利用连接对象创建preparedStatement对象,但是跟据我们的配置以及结果集类型的不同,会调用连接对象connection不同的重载的prepareStatement()方法来获取PreparedStatement对象

比如:select * from user where id = #{id};这条查询语句,它的结果集类型就是ResultSetType.DEFAULT所以会来到else if{}中,执行 connection.prepareStatement(sql)方法,此时就会对select * from user where id = ?进行预编译处理了

如果是select * from user where id = ${id};语句,它的结果集类型也是ResultSetType.DEFAULT,也会来到else if{}中,不过此时的sql语句已经是完整的,经过参数值填充的了

如果我们使用#{}预编译占位符,就调用PreparedStatement对象的set方法设置参数值

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
    // 获取连接对象
  Connection connection = getConnection(statementLog);
    // 获取Statement对象
  stmt = handler.prepare(connection, transaction.getTimeout());
    // 如果使用了#{},就调用preparedStatement的set方法完成参数的设置
  handler.parameterize(stmt);
  return stmt;
}
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);
    }
      // 如果结果集类型是DEFAULT,直接调用prepareStatement(sql)方法
  } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}

至此,mybatis的执行流程就大致分析完了~~~

举报

相关推荐

0 条评论