Mybatis源码解析之四 mybatis执行流程

目录

 

一、缘起

二、示例代码

  1、mybatis单独测试操作代码

   2、spring与mybatis的整合操作代码

3、 总结

三、mybatis执行流程

 1、mybatis的架构设计图

 2、mybatis的执行流程图

 3、mybatis执行流程时序图

 4、Mybatis的核心组件

 5、mybatis执行流程的分析


一、缘起

     前三篇的文章主要介绍了Mybatis配置以及在spring的中整合中核心组件SqlSessionFactory对象的创建过程(其中包括了相关子节点的加载,Mapper.xml的加载)。这篇博文主要用来介绍针对Mybatis的curd操作的整个执行流程。因为mybatis单机是对jdbc操作的封装,笔者的分析只到jdbc操作流程(算是mybatis源码的浅尝辄止)。

二、示例代码

  1、mybatis单独测试操作代码

 @Test
    public void testMapper() throws IOException {
        String resource="mybatis-config.xml";
        //以流的方式获取recource(mybatis的环境配置文件)
        InputStream inputStream= Resources.getResourceAsStream(resource);
        //创建会话工厂
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
        //通过工厂得到SqlSession
        SqlSession sqlSession= sqlSessionFactory.openSession();
        //从创建的SqlSession对象中获取Mapper对象 (这个是核心,因为只有拿到该对象的Mapper对象
       //(其实是Mapper接口的代理MapperProxy对象实例))
        BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);

        int bookId = 1000;
        BookExample bookExample = new BookExample();
        bookExample.createCriteria().andIdEqualTo(bookId);
        List books = bookMapper.selectByExample(bookExample);

    }

   2、spring与mybatis的整合操作代码

	@Autowired
	private BookMapper bookDao;

	@Test
	public void testQueryById() throws Exception {
		int bookId = 1000;
		BookExample bookExample = new BookExample();
		bookExample.createCriteria().andIdEqualTo(bookId);
		List books = bookDao.selectByExample(bookExample);
	}

3、 总结

          如上两种方式前者使用代码显示的从SqlSession对象中获取Mapper代理对象,后者则是在spring容器启动后将Mapper对象创建放入Spring容器中进行托管。这里有个疑问在于我们编写的XxxMapper为接口,但是在使用过程中初始化了其相应的Mapper对象。这里我们就来分析一下。

  3.1、MapperProxy对象

       这里不卖关子了,其实Mybatis为我们动态的生成了XxxMapper对象的的实际调用对象为MapperProxy对象,熟悉JDK动态代理的同学都知道接口中所有的实现都是调用实现了InvocationHandler对象的invoke()方法,我们所有对XxxMapper接口中方法的调用最终都会调用invoke()方法。该对象生成过程之间略有差异,前者是使用DefaultSqlSession的getMapper方法来生成,而后者则是使用SqlSessionTemplate对象的getMapper()方法生成。这里我们来对该MapperProxy对象初始化来做分析。

    public  T getMapper(Class type) {
        //通过Configuration对象获取其中的mapper
        return this.getConfiguration().getMapper(type, this);
    }

  configuration对象是我们mybatis的所有配置的对象,通过调用其getMapper()方法来获取MapperProxy,但是实际操作是被委托给了MapperRegistry对象

public  T getMapper(Class type, SqlSession sqlSession) {
  //通过configuration对象中的MapperRegistry对象调用其中的getMapper方法
  return mapperRegistry.getMapper(type, sqlSession);
}

   mapperRegistry根据对应的XxxMapper类class信息以及对应的mybatis会话sqlSession对象来获取dao层代理对象。还使用到了我们初始化Mapper.xml对象的时候将其以 XxxMapper className为key,MapperProxyFactory为value的形式存放在Map类型的结构对象knownMappers中,所以此处获取到对应的MapperProxyFactory对象(该对象中包含了XxxMapper接口类信息),通过Jdk动态代理生成XxxMapper接口的动态dialing对象

public  T getMapper(Class type, SqlSession sqlSession) {
   //从map容器中获取MapperProxyFactory对象信息 
   final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //调用map中获取的MapperProxyFactory对象 实例化Mapper动态代理对想
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }


//JDK动态代理实例化dao接口对象

 protected T newInstance(MapperProxy mapperProxy) {
    //jdk动态代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //采购员别废话吧MapperProxy对象
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    //dao接口实例化
    return newInstance(mapperProxy);
  }

三、mybatis执行流程

     1、mybatis的架构设计图

Mybatis源码解析之四 mybatis执行流程_第1张图片

  (嘻嘻,图是盗了csdn一位大牛的图 ,盗图地址:https://blog.csdn.net/luanlouis/article/details/40422941)

 2、mybatis的执行流程图

Mybatis源码解析之四 mybatis执行流程_第2张图片

 3、mybatis执行流程时序图

Mybatis源码解析之四 mybatis执行流程_第3张图片

 4、Mybatis的核心组件

     以相关的执行流程梳理出来的一个重要的java对象

  •   SqlSession            作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  •   Executor              MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  •   StatementHandler   封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  •   ParameterHandler   负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  •   ResultSetHandler    负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  •   TypeHandler          负责java数据类型和jdbc数据类型之间的映射和转换
  •   MappedStatement   MappedStatement维护了一条节点的封装, 
  •   SqlSource            负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  •   BoundSql             表示动态生成的SQL语句以及相应的参数信息
  •   Configuration        MyBatis所有的配置信息都维持在Configuration对象之中。

5、mybatis执行流程的分析

    二中讲述了Mapper接口的生成过程中提到了MapperProxy对象,所有Mapper接口的调用都最终会到达MapperProxy 的invoke()方法,这里也是我们进行mybatis执行过程分析的入口。

 //所有dao层接口的调用最终会到达该方法中进行处理
 // proxy为动态代理对象 Method为Dao接口层最终调用的具体方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //根据method从缓存中获取对应的MapperMethod对象,
    //如果没有创建MapperMethod 并将其放入缓存中并返回
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //最终方法的调用交于MapperMethod对象
    return mapperMethod.execute(sqlSession, args);
  }

从上面的代码可以看出获取MapperMethod对象,该对象通过execute()来执行真正的数据库调用。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      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方法 insert|delete|update|select来进行不同的分支操作,分支操作主要有两点:1、将对应的参数转换为sql参数,2、根据不同的curd操作分别调用SqlSession对象的对应方法(insert(),update(),delete()等...)。这里以查询为例子调用DefauleSqlSession的selectList()方法

 //以查询为例,查询集合列表
//1、statement 标识XxxMapper.xml文件的某一个curd方法的id 例如 
//select id="selectByExample" parameterType="bookExample" resultMap="BaseResultMap">
//中的statement为 全限定类名+selectByExample构成了一个statement
//2、parameter 参数
//3、RowBounds  分页相关的对象  
  @Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据statement (statement的唯一标识)
      MappedStatement ms = configuration.getMappedStatement(statement);
      //最终调用Executor执行器组件来实现对应的sql查询操作
      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,该对象是mybatis的执行器我们实际进行的增删改查都是通过这个Executor来进行的,

主要有两个实现类:BaseExecutor和CachingExecutor,CachingExecutor用于二级缓存,而BaseExecutor则用于一级缓存及

基础的操作BaseExecutor对象下面也有三个子类分别是:

  • SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;
  • BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
  • ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。
可以在mybatis的配置文章中进行设置使用什么类型的Executor 默认是SimpleExecutor

    
      

    

有关Executor对象的相关只是参考:mybatis的Executor相关说明   

//调用执行器的query方法来执行数据库的底层操作。其中最后一个参数为ResultHandler对查询出来的
//结果集合进行处理的对象
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取包含动态sql的BoundSql对象
    BoundSql boundSql = ms.getBoundSql(parameter);
    //根据Statamenr、参数,RowBounds(分页对象),boundSql(动态sql) 创建一级缓存所使用的key
    //在后面对查询出来的结果进行缓存的时候使用
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //查询操作
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }


 public  List 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.");
    }
    //当queryStack ==0 并且配置了flushCache 为true 则每次调用的时候刷新缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List list;
    try {
      //每调用一次自增
      queryStack++;
      //从一级缓存中获取对应的结果信息,如果有则返回,否则从数据库查询
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        //这里的处理主要针对存储过程调用的处理
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //从数据库中进行查询操作
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //结束字典,暂时不清楚queryStack的作用
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      //如果配置了本地缓存的范围为Statement 而非SqlSession 也需要情况缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

抛出缓存的问题不表,我们关注的一下执行器的 queryFromDatabase()方法,从数据库中获取数据结果

//通过数据库进行查询 
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    //使用占位形式保存数据到一级缓存中
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //调用Executor对象的doQuery方法 在BaseExecutor的doQuery()为抽象方法,
      //需要使用子类的实现调用,这里默认使用SimpleExecutor对象 
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //移除占位符(感觉此处多此一举)
      localCache.removeObject(key);
    }
    //将结果存放在一级缓存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      //对于存储过程,存放其参数信息在localOutputParameterCache中
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

查询SimpleExecutor的doQuery()方法

//SimpleExecutor子类执行 
public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      //获取mybatis的配置信息对象Configuration
      Configuration configuration = ms.getConfiguration();
      //使用configuration实例化一个StatementHandler对象 该对象主要是用来构建Statement对象的
      //StatementHandler的作用就是先通过prepare方法构建一个Statement对象,然后再调用其他方法对 
      //Statement对象进行处理
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //使用ResultHandler构造Statement对象stmt
      stmt = prepareStatement(handler, ms.getStatementLog());
      //调用Statement query()方法
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

调用StatementHandler对象 子类 PreparedStatementHandler对象,我们最常用的也是预编译的sql statement处理,所以调用其query

  @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //jdbc statement的execute() 这里获取到的结果对象Resultset 被存储了供下面代码进行
    //结果映射处理的时候使用
    ps.execute();
    //使用ResultsetHandler 来处理结果映射
    return resultSetHandler. handleResultSets(ps);
  }

结果映射的处理实现分析,调用DefaultResultSetHandler对象来对相应的结果进行解析处理

ResultSetHandler负责处理两件事:

(1)handleResultSets(Statement stmt) 处理Statement执行后产生的结果集,生成结果列表 

(2) handleOutputParameters(CallableStatement cs) 处理存储过程执行后的输出参数

这里我们只探究普通sql的执行过程,不探究存储过程的研究

  @Override
  public List handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    //结果集合转换成的java对象的封装
    final List multipleResults = new ArrayList();

    int resultSetCount = 0;
    //获取结果集合相关信息 并包装成ResultSetWrapper对象,其包含了结果集合的相关信息
    //一般情况下都是获取一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    
    //获取对应的xxxMapper.xml方法的某个标签下配置的resultMap属性(可以多个,所以为集合)
    List resultMaps = mappedStatement.getResultMaps();
    
    int resultMapCount = resultMaps.size();
    //校验resultMap是否存在
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      //获取查询结果集对应的映射关系ResultMap对象,对应我们xxxMapper.xml中的reusltMap标签 
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //将集合集合根据reusltMap的映射关系,映射为实体对象并存放到multipleResults集合中
      handleResultSet(rsw, resultMap, multipleResults, null);
      //获取下一个结果集对象信息
      rsw = getNextResultSet(stmt);
      //清空相关的缓存信息
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    //resultsets则是对获取对应的xxxMapper.xml方法的某个标签下配置的resultsets属性的封装
    //笔者没使用过该resultset对象
    String[] resultSets = mappedStatement.getResulSets();
    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源码分析)