水煮MyBatis(三)- SQL解析
前言
在Mapper接口中,有注解的方法,也有xml配置的方法,这一章我们主要介绍前者。
本文介绍的Mapper方法
@Select("select * from tb_image where md5 = #{md5}")
ImageInfo byMd5(@Param(value = "md5") String md5);
序列图
注册Mapper
注册Mapper,将Mapper注册到knownMappers中,注意后面的是一个代理类,后续再开个单章介绍。同时将Mapper接口解析,代码中的config参数是Configuration的实体,主要是mybatis的配置信息。
public <T> void addMapper(Class<T> type) {
// 注册
knownMappers.put(type, new MapperProxyFactory<>(type));
// 主要代码就是这两行
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析
parser.parse();
}
解析Mapper
Mapper里的每个方法都会创建一个statement,供后续执行使用。
解析Mapper中的方法
public void parse() {
String resource = type.toString();
// 加载xml配置,在XMLMapperBuilder中解析xml中的方法,这里忽略不讲
loadXmlResource();
// 每个Mapper加载一次
configuration.addLoadedResource(resource);
// 当前Mapper的命名空间
assistant.setCurrentNamespace(type.getName());
// 缓存注解解析,见后续单章
parseCache();
parseCacheRef();
// 每个方法都需要生成一个statement
for (Method method : type.getMethods()) {
// 忽略不能解析的方法,比如抽象、静态等
if (!canHaveStatement(method)) {
continue;
}
// 生成statement,保存到configuration
parseStatement(method);
}
}
主要流程分为三个步骤:
- 解析Mapper对应的xml文件,并为xml文件里的每个方法都生成一个statement;
- 解析缓存注解,后续介绍;
- 解析注解方法;
生成statement
public MappedStatement addMappedStatement(...) {
// 是否查询方法
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 生成statement
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
// 语句的参数配置
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// Mapper里每个方法都会生成一个statement
MappedStatement statement = statementBuilder.build();
// 将statement放到mybatis上下文
configuration.addMappedStatement(statement);
return statement;
}
将statement注册到Configuration里的mappedStatements结构中,后续执行的时候,直接从这里获取对应方法的statement
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
替换sql中的占位符
在SqlSourceBuilder类中,对sql中的参数占位符进行替换
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
// 从handler中获取请求参数map
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
例子中的请求参数:
ParameterMapping{property='md5', mode=IN, javaType=class java.lang.String, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
替换效果如下: