在上面兩節(jié)中我們解決了關(guān)于NodeHandler和SqlNode的技術(shù)債程癌,本章我們繼續(xù)來(lái)還關(guān)于SqlSource和ParameterMapping的技術(shù)債。
1. SqlSource源碼解析
這是一個(gè)接口,非常簡(jiǎn)單,我們來(lái)看下源碼:
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
最主要的是我們來(lái)看它的子類:
在之前我們看過(guò)關(guān)于RawSqlSource的源碼,接下來(lái)我們來(lái)看下比較難得動(dòng)態(tài)的DynamicSqlSource源碼承疲,我們接下來(lái)一起展示:
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
第一個(gè)要注意的就是DynamicContext類邻耕,這個(gè)類具體是干嘛的呢?我們不進(jìn)入源碼進(jìn)行分析燕鸽,如果你有好奇心赊豌,可自行調(diào)試。這個(gè)類的作用是存儲(chǔ)parameterObject相關(guān)的信息的绵咱,方便拿取parameterObject對(duì)應(yīng)信息碘饼。
第二個(gè)要注意的就是SqlSourceBuilder類了,我們進(jìn)入源碼看下悲伶,先看構(gòu)造方法和屬性:
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// <1> 創(chuàng)建 ParameterMappingTokenHandler 對(duì)象
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// <2> 創(chuàng)建 GenericTokenParser 對(duì)象
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// <3> 執(zhí)行解析
String sql = parser.parse(originalSql);
// <4> 創(chuàng)建 StaticSqlSource 對(duì)象
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
}
這里又涉及到了一個(gè)內(nèi)部類ParameterMappingTokenHandler:
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();
private Class<?> parameterType;
private MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}
}
看了這個(gè)類是不是有點(diǎn)找到思路了艾恼,因?yàn)槲覀兛吹搅嗽趆andleToken方法中我們返回了?麸锉,這個(gè)和我們創(chuàng)建動(dòng)態(tài)Sql是不是有點(diǎn)關(guān)聯(lián)了钠绍?
這里我們看到了ParameterMapping類,我們也先來(lái)看下
2. ParameterMapping源碼學(xué)習(xí)
這里我們目前就只介紹它的屬性和構(gòu)造方法花沉,他內(nèi)置的Builder涉及到的方法太多柳爽,并且也都是取值設(shè)置值,不影響心情還是不貼了:
public class ParameterMapping {
private Configuration configuration;
private String property;
private ParameterMode mode;
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;
private ParameterMapping() {
}
}
我們?cè)賮?lái)看構(gòu)造出ParameterMapping的方法buildParameterMapping:
在這個(gè)方法中我們主要講下流程:
- 解析傳進(jìn)來(lái)的content內(nèi)容碱屁,看是否有變量磷脯,如果有從對(duì)應(yīng)參數(shù)類中獲取這個(gè)屬性的類型,
- 解析其存在他其他如javaType等參數(shù)設(shè)置到ParameterMapping內(nèi)部的Builder方法中
- 通過(guò)Builder構(gòu)建ParameterMapping
3. SqlSourceBuilder類總結(jié)
我們來(lái)總結(jié)這個(gè)類的作用娩脾。在這個(gè)類的解析方法中赵誓,我們會(huì)把整個(gè)content這樣的動(dòng)態(tài)語(yǔ)句放進(jìn)去,之后通過(guò)GenericTokenParser掃描在sql中的#{}括起來(lái)的變量柿赊,把它變成俩功?,這個(gè)流程大家應(yīng)該比較熟悉碰声,之后呢诡蜓?把它封裝成ParameterMapping類來(lái)記錄我們?nèi)〕鰜?lái)的,用#{}括起來(lái)的屬性的相關(guān)信息胰挑,最后生成一個(gè)靜態(tài)的StaticSqlSource蔓罚。這樣一部操作,我們整個(gè)動(dòng)態(tài)Sql就已經(jīng)生成完畢了洽腺。
4. 今日總結(jié)
今天我們分析了關(guān)于MyBatis解析動(dòng)態(tài)sql中的#{}解析的過(guò)程脚粟,希望大家能有所收獲~~~~