*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SqlSourceBuilder
前面我們對SqlSource和SqlNode進行了介紹,在經過SqlNode.apply方法的解析之后,Sql語句會被傳遞到SqlSourceBuilder中進行進一步的解析。SqlSourceBuilder主要完成了兩方面的操作,一方面是解析Sql中的#{}占位符定義的屬性,如jdbcType、javaType(使用較少),一方面是把#{}占位符替換成?占位符
SqlSourceBuilder代碼如下
/**
-
用于進一步解析SqlSource中的${}
-
@author Clinton Begin
*/
public class SqlSourceBuilder extends BaseBuilder {private static final String PARAMETER_PROPERTIES = “javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName”;
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}/**
- 解析#{}
- @param originalSql 被SqlNode.apply解析后的sql
- @param parameterType 參數類型
- @param additionalParameters DynamicContext.bindings集合
- @return
*/
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 = parser.parse(originalSql);
// 最終被解析成含有?的靜態sql。創建StaticSqlSource,包含這個sql和參數
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
/**
-
內部類,是解析#{}的核心
*/
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {/**
- 記錄解析后得到的parameterMapping集合
/
private List parameterMappings = new ArrayList<>();
/* - 參數類型
/
private Class<?> parameterType;
/* - DynamicContext.bindings對應的類
*/
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 getParameterMappings() {
return parameterMappings;
}/**
- 占位符處理器核心方法。
- @param content
- @return
*/
@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();
// 處理jdbcType、javaType等一大堆東西
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);
}
}
} - 記錄解析后得到的parameterMapping集合
}
[點擊并拖拽以移動]
其中,ParameterMappingTokenHandler是SqlSourceBuilder的一個內部類,該類是解析#{}的核心。
ParameterMapping中記錄了#{}占位符中的參數屬性,字段如下
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;
}
之后,SqlSourceBuilder會將Sql語句以及parameterMap平時集合封裝成StaticSqlSource對象。StaticSqlSource.getBoundSql方法直接返回BoundSql,BoundSql代碼如下。
/**
-
Sql實體。包含sql和 參數集合
-
@author Clinton Begin
*/
public class BoundSql {private final String sql;
/**- SQL中參數屬性集合#{item}這些
/
private final List parameterMappings;
/* - 執行SQL時傳入的實際參數
/
private final Object parameterObject;
/* - DynamicContext.bindings集合
/
private final Map<String, Object> additionalParameters;
/* - additionalParameters對應的MetaObject
*/
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}public String getSql() {
return sql;
}public List getParameterMappings() {
return parameterMappings;
}public Object getParameterObject() {
return parameterObject;
}public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
} - SQL中參數屬性集合#{item}這些
DynamicSqlSource
DynamicSqlSource負責解析動態SQL語句,也是最常用的SqlSource實現。DynamicSqlSource使用rootSqlNode字段,記錄了帶解析的SqlNode根節點DynamicSqlSource的代碼如下。
/**
-
負責解析動態sql語句
-
包含#{}占位符
-
@author Clinton Begin
*/
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);
// 解析sql節點
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// 解析sql,將#{}替換成?
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
RawSqlSource
RawSqlSource的邏輯和DynamicSqlSource類似,但是處理SQL的機制不同。RawSqlSource用于處理非動態SQL。當一個sql中只包含#{}占位符。不包含${}和動態sql節點,就不是動態SQL語句,會創建相應的StaticTextSqlNode在XmlScriptBuilder.parseScriptNode方法會判斷整個SQL節點是否是動態的,如果是動態,就用DynamicSqlSource進行處理,否則用RawSqlSource進行處理。
RawSqlSource在構造方法中會調用getSql方法,該方法會調用SqlNode.apply方法完成sql語句的處不處理。SqlSourceBuilder完成占位符的替換,并返回StaticSqlSource對象。
/**
-
處理非動態sql語句
-
如果節點只包含“#{}”占位符,而不包含動態 SQL 點或未解析的 “${}”占位
-
符的話, 則不是動態 SQL 語句
-
@author Eduardo Macarron
-
@since 3.2.0
*/
public class RawSqlSource implements SqlSource {private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
像foreach、if、where等標簽,以及${}占位符,在mybatis初始化時并不知道其具體含義,因此這類sql就視為“動態sql”,交由DynamicSqlSource在程序運行時進行解析。而如果只含有#{}占位符,則會在mybatis初始化時就完成sql解析。
*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新