mybatis执行流程的源码分析

mybatis执行流程的源码分析

一、通过SqlSessionFactoryBuilder类创建SqlSessionFactory。
//配置文件
        InputStream configFile = new FileInputStream(
                projectFile+"\\src\\main\\java\\com\\gupaoedu\\mybatis\\demo\\mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile);
二、再通过SqlSessionFactory取获取sqlSession。
sqlSessionFactory.openSession();
三、具体的执行流程。

众所周知mapper是一个接口,如果需要执行sql需要拿到xml配置文件或者annotation注解的sql语句,那么这里肯定用到了动态代理。在sql语句在创建SqlSessionFactory时,将配置文件或者注解配置到注册中心( 类Configuration)里去,在mapper执行sql语句时,通过动态代理获取。下面四、五、六点分别讲注册、动态代理执行、返回结果映射。

四、注册mapper到注册中心( 类Configuration),下面以xml方式举例。

类 SqlSessionFactoryBuilder

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());//重要的是这句代码,通过XMLConfigBuilder的parse方法将会扫描mybatis-config.xml里<mappers></mappers>的需要注册的*Mapper.xml文件。
    } 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.
      }
    }
  }

下面接着看类 XMLConfigBuilder

/*
*被调用parse方法,parse方法再调用parseConfiguration方法。
*/
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
/*
*其中mapperElement(root.evalNode("mappers"));
*这句话就是获取<mappers></mappers>的需要注册的*Mapper.xml文件。
*/
  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"));//获取<mappers></mappers>的需要注册的*Mapper.xml文件。
    
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

下面是真正起作用的mapperElement 方法。调用 configuration.addMappers(mapperPackage);

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);//将mapper注册到configuration
        } 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);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);//将mapper注册到configuration
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

configuration.addMappers实际调用的是类MapperRegistry里的addMappers方法,具体细节不再赘述。

public void addMappers(String packageName, Class<?> superType) {
    mapperRegistry.addMappers(packageName, superType);
  }

  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
五、通过动态代理执行mapper。

TestMapper为一个mapper接口。getMapper实际拿到为代理模式产生的一个动态代理对象。

TestMapper testMapper = getSqlSession().getMapper(TestMapper.class);

SqlSession有个两个实现类。SqlSessionManager(线程安全)和DefaultSqlSession(线程不安全),猜想mybatis用的策略模式将其区分。整合spring后用的是类 SqlSessionTemplate 获取。

具体线程安全问题参见文章 《MyBatis 中 SqlSession 是线程安全的吗?》 和 《Mybatis 不是有 SqlSessionManager 了吗?为什么又提供了SqlSessionTemplate?

这三个SqlSession的实现类都是通过类Configuration获取mapper的代理类。

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

最终还是要通过MapperRegistry的getMapper方法。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //之前addMapper时,已将放置不同类型的mapper的mapperProxyFactory放入到集合knownMappers中。
    //key为:mapper的class类型。value为:MapperProxyFactory对象。
    //MapperProxyFactory看字面意思就知道其为mapper动态代理对象的工厂类。
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    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);
    }
  }

接着往下看MapperProxyFactory

 @SuppressWarnings("unchecked")
  /**
  *这里创建了一个jdk动态代理,实际处理类为MapperProxy。
  */
  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<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

再往下来到真正的实际执行处理类MapperProxy。看到这里的invoke方法,跟传统的代理模式不一样,传统的代理模式应该执行被代理类的方法,由于mybatis的sql语句的方式为xml配置文件或者注解方式。所以mybatis的代理是一个阉割版的代理方式

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   /**
   *传统的代理模式,这里应该执行被代理方法。而mybatis这里自己封装了一个MapperMethod去执行sql。
   *示例:method.invoke(被代理类,方法参数);
   */
   try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);//这里是执行的sql的实际方法。
  }

下面再看sql具体的执行方法,以select举例。先看MapperMethod的执行方法execute。

/**
*根据策略模式,选择不同增删改查对应的策略对象。最终还是选择相对应sqlSession里的CRUD方法。
*/
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);
        //这里执行SqlSession的update方法。
        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;
  }

这里重新执行SqlSession的update方法,以其实现类DefaultSqlSession举例。

mybatis的sql执行都封装到了Executor里。

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //MappedStatement维护了一条<select|update|delete|insert>节点的封。
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

接着再往下看Excutor如何执行。这里以其实现类BaseExecutor举例。

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

BaseExecutor是一个抽象类,显然用的是模板模式,这里的query是一个模板方法。真正去执行的是其子类,这里用子类SimpleExecutor去分析。中间打断点过程略过,来到实际执行的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());
      //这里用封装好的声明处理类StatementHandler来执行。
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

这里拿一个StatementHandler子孙类StatementHandler举例。这里没什么好解释的,就是jdbc的statement执行,如果不了解可以去查看jdbc的使用方法。最终将返回结果用ResultSetHandler进行处理映射,下面第6点详细讲。

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    //调用jdbc的statement的执行
    statement.execute(sql);
    //这里用ResultSetHandler将返回结果进行处理映射。
    return resultSetHandler.<E>handleResultSets(statement);
  }
六、通过ResultSetHandler对结果进行处理。

接口ResultSetHandler的唯一实现类DefaultResultSetHandler。

public interface ResultSetHandler {

   //将返回结果解析成List集合,E对象为泛型定义的实际返回类型。
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  //将返回结果解析成Cursor对象,为Mybatis 3.4.0 新增的功能,使用游标提升性能。
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  //处理存储过程。
  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

ResultSetHandler的唯一实现类DefaultResultSetHandler。这里只针对第5点中用到的handleResultSets进行分析,不影响整个流程的梳理。

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
	//ResultSet结果集集合,对于普通查询只能返回一个ResultSet,存储过程可能会返回多个。
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    //ResultSet装饰类
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      /**
      *handleResultSet为实际的处理方法。实现细节不再分析。
      *主要执行过程为:
      *1.处理ResultSet返回的每一行Row,里面会循环处理全部的结果集。
      *2.将处理后的结果,添加到multipleResults中。
      */
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

以上为编程式mybatis执行流程的源码分析。Thanks!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值