小白之mybatis源码分析-2

  • 小白之mybatis源码分析-2

上文小白之mybatis源码分析-1
讲到MapperRegistry类的addMapper,大家大概也有个大体的了解,其中过程主要是解析xml注解生成MappedStatement对象,便于获取执行确定要执行的sql,和其他设置如缓存等。

  • 分析MapperRegistry的getMapper方法

MapperRegistry类除了addMapper最重要以外,getMapper就是第二重要的,getMapper涉及的代码可能没有addMapper那么多,但是getMapper是Mapper类接口怎么具有sql执行能力的关键。如果有一点基础的同学们,可以猜到Mapper接口类怎么变的可以执行,没错就是代理代理类在各种框架中非常常见,Mybatis使用了代理也是意料之中。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //获取Mapper类对应的的静态工程类
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      //Mapper类没有注册
      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静态工程类
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

MapperProxyFactory静态工程类是生成Mapper类对应的MapperProxy的静态工程类,
我们可以大体的看下其源代码.见下

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //通过JDK 代理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //生成Mapper代理类
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    //建立代理关系
    return newInstance(mapperProxy);
  }

}

这里也是简单的建立Mapper和MapperProxy的代理关系,建立代理关系了,就可以由 MapperProxy代理Mapper具有sql的执行能力,不过这里可以关注一下methodCache 这个元素

  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

后文会继续涉及这个元素,这个元素是缓存了Mapper接口类的每个Method对应的MapperMethodInvoker,Mapper类的每个Method最终会由MapperMethodInvoker进行代理。

//代理工程生成 代理类代理Mapper的方法
return mapperProxyFactory.newInstance(sqlSession);

从代理类的生成,我们继续深入代理的整个过程
在这里插入图片描述
可以得知是将mapperInterface代理给mapperProxy在这里插入图片描述
mapperInterface就是mapper类,具体可参见addMapper方法中
在这里插入图片描述
要指定这些mapper接口是怎么被代理,就要查看MapperProxy的invoke方法(由于是接口代理的就是JDK代理实现InvocationHandler接口)在这里插入图片描述
查看下invoke方法

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        //Object基类的方法不代理
        return method.invoke(this, args);
      } else {
        //代理Mapper类的接口方法
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

可以看见MapperProxy 代理工作将会在cachedInvoker(method).invoke(proxy, method, args, sqlSession);执行。cachedInvoker(method)从名字也可以得出这个跟缓存相关的,就是上文提到的methodCache 有关,虽然他们一个存在与MapperProxy一个存在于MapperProxyFactory,但是由于是由MapperProxyFactory将methodCache对象的引用传递给MapperProxy,所以他们是同一个对象。

我们看下cachedInvoker(method)是怎么执行的

  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);
      if (invoker != null) {
        return invoker;
      }
       //缓存不存在解析代理Mapper放入缓存
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          //该方法是不是 default方法
          try {
            if (privateLookupInMethod == null) {
              //privateLookupInMethod 判断MethodHandles类中是否有privateLookupIn方法,该方法是java9中才有的  无
              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;
    }
  }

可以发现PlainMethodInvoker或者DefaultMethodInvoker来完成代理实现的工作了
在看看它们的定义,他们都是定义在MapperProxy类中(高内聚)

interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

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

  private static class DefaultMethodInvoker implements MapperMethodInvoker {
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
      super();
      this.methodHandle = methodHandle;
    }

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

可以看出DefaultMethodInvoker是代理成Mapper中的default方法(JDK8以后接口可以具有默认方法),PlainMethodInvoker是代理Mapper中的接口方法也就是要执行sql的方法。

  • PlainMethodInvoker的代理过程
    介绍PlainMethodInvoker的代理过程,首先要看下它的构造行数中的MapperMethod
    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

在这里插入图片描述
从上图可以得知就两个属性一个是SqlCommand就是sql命令的类型如select,update等,MethodSignature就是方法的签名。
现在点击代理的实现
在这里插入图片描述


  public Object execute(SqlSession sqlSession, Object[] args) {
    //结果集
    Object result;
    //sql 命令类型
    switch (command.getType()) {
      //插入
      case INSERT: {
        //方法参数转化为Sql命令的参数 param将会变成Map结构
        Object param = method.convertArgsToSqlCommandParam(args);
        //执行 sqlSession.insert 结果集包装
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //更新
      case UPDATE: {
        //方法参数转化为Sql命令的参数 param将会变成Map结构
        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;
  }

看到这里会觉得的一片光明,重要到了代理sql的方法了。
这里就举一个INSERT类型的代理的实现把,其他也差不多。

  public int insert(String statement, Object parameter) {
    //复用update
    return update(statement, parameter);
  }
   public int update(String statement, Object parameter) {
    try {
      //脏数据
      //此时事务未提交,
      dirty = true;
      //从 Configuration类获取该Mapper对应的MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //rapCollection(parameter)将集合类型,list,数组类型包装成Map 执行 executor.update
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

mybatis默认删除插入更新操作要删除缓存

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //删除本地的缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

doUpdate有多个实现类我们就选最简单的SimpleExecutor
在这里插入图片描述

看到Statement 了吗,说明最终通过jdbc实现代理很近了,这里还有mybatis拦截器对要执行的StatementHandler 进行拦截,mybatis拦截器常用比如修改自动补全操作时间等操作

  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //mybatis拦截器链链存在
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

这里要一下StatementHandler 其实他就是Statement再次包装的工具类

public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

stmt = prepareStatement(handler, ms.getStatementLog());中使用StatementHandler类对象进行数据库连接(Connection connection = getConnection(statementLog))和动态sql参数绑定(handler.parameterize(stmt))

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

由于本次是PrepareStatement我们就查看PrepareStatementHandler的parameterize方法看下如何绑定参数
在这里插入图片描述
。。。。又套娃

  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

再点进去看看

  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

TypeHandler 这个要注意以下他是Mybatis在PrepareStatement传统设置的情况加上自己类型转化的逻辑。到这里一个完整的PrepareStatement就生成了,后面就是执行了。

终于到了执行了

在这里插入图片描述
再点击进去,就是直接执行和KeyGenerator 的一下信息拦截,到这就完成一个getMapper的所有工作,当然不同的mapper可能又不同的走向,大致流程是一样的。

  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    //KeyGenerator 拦截 
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值