MyBatis3源码阅读(二)2020-12-24

SELECT的执行过程

前言

前一节我们通过源码认识了SqlSessionFactoryBuilder、SqlSessionFactory 和 SqlSession的创建过程,开胃菜算是尝过了,今天该开始吃正餐了,因为想把详细的过程呈现和记录下来,所以本篇有过多的截图,请保持耐心,我会在每一步下有详细的理解。 

step1:得到sqlSession对象,创建与表users对应的pojo对象users,通过setUserName("king")赋值, 获取mapper接口对象,调用方法接口

测试代码如下:

  public static void exampleTest1(){
    SqlSession session = sqlFactory.openSession();
    Users user = new Users();
    user.setUserName("king");
    UserMapper u = session.getMapper(UserMapper.class);
    Users user1 = u.getUser(user);
    System.out.println(user1);
    session.commit();
    session.close();
  }

结果如图:

完整的代码,过程就不贴了,随便百度百度都是,直接上源码:

step2:第一处断点的位置,UserMapper u = session.getMapper(UserMapper.class);

1、

2、进入的是DefaultSqlSession 类  

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

3、

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

4、

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

5、

  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<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

总结性话语:上面的五步就是通过判断传入的.class是否注册过,在本例中为session.getMapper(UserMapper.class);,如果注册了就通过动态代理的方式获取对象返回,否则第4步会抛异常处理。

step3:到这里就已经得到userMapper对象了,下一处的断点位置Users user1 = u.getUser(user);

1、

2、进入MapperProxy.java,调用cacheInvoker方法实现数据的封装,首先判断在缓存中是否有当前方法的代理,如果已经封装过操作数据库所需要的数据就不需要重复执行这个过程,直接返回调用invoke即可,不存在的话就先判断我们当前调用的方法是不是接口中的default方法,是的话也不需要执行额外的操作,因为mybatis事先也不可能得知程序员要在default中执行的逻辑。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//proxy一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象的invoke方法在代理类中动态实现。
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//如果是Object中定义的方法,直接执行。如toString(),hashCode()等
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);//其他Mapper接口定义的方法交由mapperMethod来执行
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

//Object.class.equals(method.getDeclaringClass()这里要判断当前所调用的方法时候属于Object
//类,因为如果我们调用的方法比如hashcode()、equals等这些事继承于Object类的方法,那么mybatis就
//不需要进行动态代理了,直接执行父类的逻辑即可,这里也很容易理解,因为mybatis无法帮我们完成对
//这些方法的逻辑代理。

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929
      MapperMethodInvoker invoker = methodCache.get(method);//判断该方法在当前的session中是否已经代理过,代理过的方法会缓存在map中
      if (invoker != null) {
        return invoker;
      }

      //判断是不是接口的默认方法,是默认方法就不做代理
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

在这里插入图片描述

这里就开始封装操作数据库所需要的数据,来自两方面的内容,sql语句相关的内容以及接口方法的内容

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

这个时候调用的invoke方法与InvocationHandler的invoke方法没有什么关系,是PlainMethodInvoker的方法,而PlainMethodInvoker对象封装了所有我们所需要的数据,程序运行到这里,数据已经都准备好了,接着调用execute方法

3、进入MapperMethod.java,执行execute(),首先判断语句类型insert,update,select,delete,插入,更新,删除的操作类似都是将参数解析为sql,执行后返回行的结果,true or false, 或者 成功是1,失败是0.

查询的话比较复杂,(1):查询到的结果集为空;(2):查询到多个结果(Listl类型);(3):结果集是Map类型;(4):返回结果集游标(这个我也不太懂,下一节查了在记录下);(5)查询单一结果;

测试代码走的就是第五种形式

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

4、进入DefaultSqlSession,selectOne里调用了selectList,所以我们还是看下selectList里是怎么写的吧

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    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;
    }
  }

step4: selectList ,     

return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);//最后走的是这步,进入看看

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      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();
    }
  }

step4: 最终的执行代码就是下面的两段

  @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);
  }
  @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) {//判断Executor是否关闭
      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;//是从缓存中取值,还是设为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;
  }

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值