你在mapper.xml文件中写的sql语句最终是怎么被执行的?咱们编写的mapper接口最终是怎么生成代理对象并被调用执行的?

这部分内容应该是Mybatis框架中最要害、也是最简单的局部,明天文章的次要指标是要搞清楚:

  1. mapper.xml文件是怎么初始化到Mybatis框架中的?
  2. mapper接口生成动静代理对象的过程。
  3. mapper接口动静代理对象的执行过程。

把握了以上3个问题,咱们就把握了Mybatis的外围。

Mapper初始化过程

指的是mapper.xml文件的解析过程。

这个动作是在SqlSessionFactory创立的过程中同步实现的,或者说是在SqlSessionFactory被build进去之前实现。

XMLMapperBuilder负责对mapper.xml文件做解析,SqlSessionFactorBean的buildSqlSessionFactory()办法中会针对不同配置状况进行解析。其中咱们最罕用的是在配置文件中指定mapper.xml文件的门路(就是源码中的这个mapperLocations):

if (this.mapperLocations != null) {      if (this.mapperLocations.length == 0) {        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");      } else {        for (Resource mapperLocation : this.mapperLocations) {          if (mapperLocation == null) {            continue;          }          try {            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());            xmlMapperBuilder.parse();          } catch (Exception e) {            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);          } finally {            ErrorContext.instance().reset();          }          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");        }      }    } else {      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");    }    return this.sqlSessionFactoryBuilder.build(targetConfiguration);

创立XMLMapperBuilder对象并调用parse()办法实现解析。

XMLMapperBuilder#configurationElement()

parse办法会调用configurationElement()办法,对mapper.xml的解析的要害局部就在configurationElement办法中。

咱们明天把问题聚焦在mapper.xml文件中sql语句的解析,也就是其中的insert、update、delete、select等标签的解析。

private void configurationElement(XNode context) {    try {      //获取namespace      String namespace = context.getStringAttribute("namespace");      if (namespace == null || namespace.isEmpty()) {        throw new BuilderException("Mapper's namespace cannot be empty");      }      builderAssistant.setCurrentNamespace(namespace);    //二级缓存Ref解析      cacheRefElement(context.evalNode("cache-ref"));   //二级缓存配置的解析      cacheElement(context.evalNode("cache"));     //parameterMap标签的解析 parameterMapElement(context.evalNodes("/mapper/parameterMap"));   //resultMap标签的解析 resultMapElements(context.evalNodes("/mapper/resultMap"));   //sql标签的解析 sqlElement(context.evalNodes("/mapper/sql"));    //要害局部:sql语句的解析 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    } catch (Exception e) {      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);    }  }

对sql语句解析局部持续跟踪会发现,最终sql语句解析实现之后会创立MappedStatement并保留在configuration对象中(以xml文件中的id为key值的Map中):

MappedStatement statement = statementBuilder.build();    configuration.addMappedStatement(statement);    return statement;

这样咱们就明确了,mapper.xml中编写的sql语句被解析后,最终保留在configuration对象的mappedStatements中了,mappedStatements其实是一个以mapper文件中相干标签的id值为key值的hashMap。

Mapper接口生成动静代理过程

咱们都晓得Mapper对象是通过SqlSession的getMapper办法获取到的,其实Mapper接口的代理对象也就是在这个调用过程中生成的:

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

调用Configuration的getMapper办法:

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

调用mapperRegistry的getMapper办法,首先从knownMappers(以namespace为key值保留mapperProxyFactory的HashMap)中获取到mapperProxyFactory,mapperProxyFactory人如其名,就是mapper代理对象工厂,负责创立mapper代理对象。

获取到mapperProxyFactory之后,调用newInstance办法:

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

调用MapperProxy的newInstance办法:

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

典型的JDK动静代理生成逻辑,传入的回调参数为mapperProxy对象,也就是说咱们对Mapper接口的办法调用,最终通过生成的动静代理对象,会调用到这个回调对象mapperProxy的invoke办法。

至此,mapper接口动静代理的生成逻辑咱们就从源码的角度剖析结束,当初咱们曾经搞清楚mapper动静代理的生成过程,最重要的是,咱们晓得mapper接口的办法调用最终会转换为对mapperProxy的invoke办法的调用。

mapper接口动静代理对象的执行过程

这个问题当初曾经明确了,其实就是MapperProxy的invoke办法。

对MapperProxy稍加钻研,咱们发现他的invoke办法最终会调用MapperMethod的execute办法:

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

execute办法依据调用办法的sql类型别离调用sqlSession的insert、update、delete、select办法,相应的办法会依据调用办法名从configuration中匹配MappedStatement从而执行咱们在mapper.xml中配置的sql语句(参考XMLMapperBuilder#configurationElement()局部)。

因而咱们也就明确了为什么mapper.xml文件中配置的sql语句的id必须要对应mapper接口中的办法名,因为Mybatis要通过mapper接口中的办法名去匹配sql语句、从而最终执行该sql语句!

工作实现!

其实尽管咱们晓得SqlSession默认的落地实现对象是DefaultSqlSession,咱们在mapper.xml中编写的sql语句其实是DefaultSqlSession负责执行的,然而MapperMethod中sqlSession其实也是代理对象(DefaultSqlSession的代理对象),所以说Mybatis中到处都是动静代理,这部分咱们下次再剖析。

上一篇 MybatisL事务管理机制