浅析mybatis源码(四)执行sql流程

这篇文章主要从MapperProxy类出发,来纵向分析下Mybatis执行sql的流程。

MapperProxy类

MapperProxyFactory类只是MapperProxy的工厂类,所以主要看下MapperProxy就好了。MapperProxy是用jdk动态代理来实现对Mapper的代理,因此先看下他的invoke方法。不出意外,这个方法里就有我们最关心的代码,是如何执行的查询。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);//Object定义的方法,则透传给Mapper
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);//java8接口的默认方法和静态方法,代理方法有所不同。
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);//先从缓存中取,没有则根据method新建MapperMethod类
    return mapperMethod.execute(sqlSession, args);//执行sql
  }

可以看到真正执行Sql的是MapperMethod的execute方法。接下来看下。

MapperMethod类

MapperMethod的execute方法主要就是判断sql语句是update,insert,delete还是select,来执行不同的逻辑。update,insert,delete的逻辑基本相同,都是执行sql语句。select较为复杂,需要将返回结果映射为java bean。返回多个结果时还要分为集合,map,Cursor三种情况。因此,我们就只看select,并且只考虑将结果集保存在集合的情况。实现在executeForMany方法中。

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);//获取参数Map
    if (method.hasRowBounds()) {//逻辑分页,一般很少用
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {//若结果类型不符合方法的返回类型,则转化为对应的结合类型
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

method这个属性是MethodSignature类,是MapperMethod的内部静态类。主要封装了下处理Method的方法。convertArgsToSqlCommandParam这个方法就是当有多个参数时,将这次方法调用的参数和参数名保存在一个map中。

    @Select("select * from product where id > #{id} and price > #{price}")
    ArrayList<Product> findAll(@Param("id") long id, @Param("price") double price);

例如上面这个方法,代理类在执行这个方法时,执行到Object param = method.convertArgsToSqlCommandParam(args)这句时,若入参为1,20。则param的值为Map,键值对为:
{
"id":"1",
"price":"20",
"param1":"1",
"price":"1"
}
有了这个Map,则方便将Sql中的#{id},#{price}替换为"1"和"20"。
command这个属性是SqlCommand类,是MapperMethod的内部静态类。主要保存了sql数据,封装了一些工具方法。 getName方法,则是得到"${Mapper包路径}.${Mapper类名}.${执行方法名}",例如,若包名是mapper,上面的方法则是"mapper.ProductMapper.findAll"。这个字符串有什么用呢?
上一篇说过addMapper的过程中,主要存储下了三份数据,其中sql信息存储在MappedStatement类中,而所有的MappedStatement存储在mappedStatements这个Map中,而他的key也是这个字符串。
所以此时,我们可以找到这个方法对应的MappedStatement。即得到addMapper时解析Sql的相关数据。 可以看到最终执行Sql是SqlSession的selectList方法。mybatis默认使用的SqlSession是DefaultSqlSession类,接下来看下。

DefaultSqlSession类
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);//通过key找到对应的MappedStatement。
      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();
    }
  }

可以看到,ms便是刚刚说到的MappedStatement。而最终执行查询的是executor。executor是DefaultSqlSession的一个属性,是在构造方法中传来的。DefaultSqlSession是在哪里构造的呢?就是在第一篇中demo中调用到的,SqlSessionFactory的OpenSession方法。mybatis默认的SqlSessionFactory是DefaultSqlSessionFactory。

DefaultSqlSessionFactory类

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();//保存了transactionFactory和DataSource
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);//一个session对应一个Executor,一级缓存存在Executor里,这也就是为什么一级缓存是Session级别的。
    } 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();
    }
  }

可以看到主要分了三步:事务工厂实例化事务对象tx,实例化执行器executor,传入刚刚的tx,实例化DefaultSqlSession,传入刚刚的executor。
事务类主要考虑JdbcTransaction,这个类其实就是稍微封装了下Jdbc的commit,rollback,setAutoCommit等操作。
接下来就到了咱们要找的executor了。Executor是个接口,mybatis封装了三种实现类,BatchExecutor,ReuseExecutor,SimpleExecutor。configuration.newExecutor(tx, execType),这句代码就是根据类型来实例化不同的Executor。以及后续要说的缓存和插件扩展。
继续回到刚刚的DefaultSqlSession类,里面主要用到了Executor的query方法。接下来看下Executor吧。

Executor

在Executor中,我们能看到装饰者模式和模板模式的影子。
BaseExecutor抽象类,实现了query,commit,rollback等基本方法。但doQuery等是抽象方法,由实现类BatchExecutor,ReuseExecutor,SimpleExecutor去具体实现。这种模板模式,耦合代码较少,值得学习。
CachingExecutor是个装饰类,为类赋予缓存功能。不需要为BatchExecutor,ReuseExecutor,SimpleExecutor写三个对应的缓存类。
具体的查询代码在BatchExecutor,ReuseExecutor,SimpleExecutor的doQuery方法中,我们就来看下最简单的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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//实例化StatementHandler
      stmt = prepareStatement(handler, ms.getStatementLog());//得到jdbc中的 Statement,并进行sql参数绑定、设置超时时间、异常处理等。
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
StatementHandler

StatementHandler的结构也和Executor差不多,也是用了模板模式。SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler这三个实现类分别对应了jdbc的三种Statement,他们的区别可以查看jdbc文档。我们就主要看PreparedStatementHandler。

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;//将statement强制转换为jdbc中的PreparedStatement
    ps.execute();//执行sql
    return resultSetHandler.<E> handleResultSets(ps);//将jdbc的ResultSets转化为需要的结果类型
  }

可以看到最终执行的还是jdbc的PreparedStatement.execute()。而jdbc最终将结果保存在ResultSets中,需要resultSetHandler将其解析为最终类型。这个过程下篇文章要讲,涉及到addMapper过程中保存的ResultMap。

总结

sql执行的纵向过程如下图

当然每个类还有其他的代码,以后会对缓存,插件,日志等进行分析。有的细节也并没有去讨论,主要是因为这些代码并不算难,但又比较繁琐。例如sql参数绑定,已经将参数保存在了map中,剩下就是字符串匹配然后用jdbc的api进行参数绑定。

转载于:https://my.oschina.net/u/3361610/blog/3012898

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值