文章目录
- 1 生成 MappedStatement 的入口
- 2 MappedStatement 生成源码分析
- 2.1 checkDaoConfig() — 生成 MappedStatement 总流程
- 2.2 loadXmlResource() — 解析 xml 生成 MappedStatement
- 2.2.1 遍历所有的 sql 节点,解析对应的 mappedStatement
- 2.3 parseStatement() — 解析接口方法注解生成 MappedStatement
- 3 MappedStatement 的作用
- MappedStatement 类是 Mybatis 框架的核心类之一,它存储了一个 sql 对应的所有信息
- Mybatis 通过解析 XML 和 mapper 接口上的注解,生成 sql 对应的 MappedStatement 实例,并放入 SqlSessionTemplate 中 configuration 类属性中
- 正真执行 mapper 接口中的方法时,会从 configuration 中找到对应的 mappedStatement,然后进行后续的操作
- 本文将通过源码介绍 mappedStatement 生成的全过程
1 生成 MappedStatement 的入口
- 在 Springboot 模块分析 —— MybatisAutoConfiguration 解析 一文中,介绍了生成 mapper 接口对应 beanDefintion 的流程,并且可以看出 生成 mappedStatement 有 3 条路径
- 如果定义了 mapperLocation,在初始化 SqlSessionFactoryBean 后,回去解析 mapperLocation 对应的 xml 文件,解析生成 xml 中每一个 sql 对应的 mappedStatement 实例,并加入 configuration 中
mybatis:
mapper-locations: /xx/xx/*.xml
- 如果没有定义 mapperLocation,当解析 mapper 接口对应的 beanDefinition 后,会调用 MapperFactoryBean#checkDaoConfig() 方法,通过接口全路径名(将.java变为.xml),来找对应的 xml
- 上一步解析完了 xml 后,会去遍历 mapper 接口中的方法,如果方法中有 sql 相关注解,就会生成 sqlSource 并覆盖上一步生成的 mappedStatement!
- 综上, mappedStatement 生成的入口有 3 个,优先级为:mapperLocation > 注解 sql > 接口对应 xml 路径
2 MappedStatement 生成源码分析
- 当实例化 mapper 接口对应的 beanDefintion 后,会调用 MapperFactoryBean#checkDaoConfig() 方法进行生成,本小节详细分析源码过程
- 最后通过一些参数初始化了 mappedStatement,观察这些参数是怎么来的,可以更好的做参数设置,此处留待以后分析
- 源码中也涉及到了各个注解的解析,观察相应代码可以清楚的理解各个注解的使用,,此处留待以后分析
2.1 checkDaoConfig() — 生成 MappedStatement 总流程
protected void checkDaoConfig() {
super.checkDaoConfig();
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
configuration.addMapper(this.mapperInterface);
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
parseStatement(method);
}
}
parsePendingMethods();
}
2.2 loadXmlResource() — 解析 xml 生成 MappedStatement
- 关键-关键-关键:这里的 xml 路径是,接口的全名路径,并加后缀转为 .xml
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
}
private void configurationElement(XNode context) {
String namespace = context.getStringAttribute("namespace");
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
2.2.1 遍历所有的 sql 节点,解析对应的 mappedStatement
- 关键-关键-关键:从这里可以看出,xml sql 节点的 id 必须和方法名一致才有效
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
public MappedStatement addMappedStatement() {
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = 构建器;
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
2.3 parseStatement() — 解析接口方法注解生成 MappedStatement
- 关键-关键-关键:如果方法中有 sql 相关注解,则会覆盖 xml 中的 mappedStatement
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
assistant.addMappedStatement(mappedStatementId,sqlSource,statementType,
sqlCommandType,fetchSize,timeout,
null,
parameterTypeClass,resultMapId,getReturnType(method),resultSetType,flushCache,
useCache,
false,keyGenerator,keyProperty,keyColumn,
null,languageDriver,
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
3 MappedStatement 的作用
- MappedStatement 的作用可参考 mapper 接口方法运行流程