关于java:Mybatis的Mapper代理对象生成及调用过程

43次阅读

共计 4799 个字符,预计需要花费 12 分钟才能阅读完成。

你在 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 事务管理机制

正文完
 0