你在 mapper.xml 文件中写的 sql 语句最终是怎么被执行的?咱们编写的 mapper 接口最终是怎么生成代理对象并被调用执行的?
这部分内容应该是 Mybatis 框架中最要害、也是最简单的局部,明天文章的次要指标是要搞清楚:
- mapper.xml 文件是怎么初始化到 Mybatis 框架中的?
- mapper 接口生成动静代理对象的过程。
- 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 事务管理机制