mybatis作為持久層,其操作數據庫離不開sql語句。而BoundSql則是其保存Sql語句的對象
前提
針對mybatis的配置文件的節點解析,比如
where
/if
/trim
的節點解析可見文章Spring mybatis源碼篇章-NodeHandler實現類具體解析保存Dynamic sql節點信息針對mybatis配置文件的解析幫助類SqlSource[一般為DynamicSqlSource]的使用可見文章Spring mybatis源碼篇章-XMLLanguageDriver解析sql包裝為SqlSource
對BoundSql對象的調用獲取可見文章Mybatis源碼分析-BaseExecutor
本文將在上述的知識前提下展開對Sql語句的解析
BoundSql的引用
主要是通過MappedStatement#getBoundSql()
方法調用獲取的。我們可以簡單跟蹤下其中的源碼,如下
public BoundSql getBoundSql(Object parameterObject) {
// 通過SqlSource獲取BoundSql對象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 校驗當前的sql語句有無綁定parameterMapping屬性
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
RawSqlSource-常用的mybatis解析sql幫助類
我們觀察下其getBoundSql()方法,源碼如下
public BoundSql getBoundSql(Object parameterObject) {
//此處的sqlSource為RawSqlSource的內部屬性
return sqlSource.getBoundSql(parameterObject);
}
我們看下sqlSource是如何生成的,由此觀察其構造函數
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// 通過SqlSourceBuilder來創建sqlSource
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
#{}
的使用這里稍微提下,一般的寫法都為{name,jdbcType=String,mode=out,javaType=java.lang.String...}
,其中jdbcType也可以不指定,系統會自動識別。上述的代碼其實主要就是針對#{}
字符內容的處理注意:
${}
這樣的字符是通過DynamicSqlSource來完成解析的,具體的解析讀者可自行分析
我們可以繼續看下SqlSourceBuilder類是如何解析獲取sql語句的
SqlSourceBuilder#parse()
直接上源碼
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 對#{}這樣的字符串內容的解析處理類
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 獲取真實的可執行性的sql語句
String sql = parser.parse(originalSql);
// 包裝成StaticSqlSource返回
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
簡單的看下ParameterMappingTokenHandler是如何解析的,其是TokenHandler
接口的實現類,我們就關注實現方法handleToken
@Override
public String handleToken(String content) {
// 此處的作用就是對`#{}`節點中的key值保存映射,比如javaType/jdbcType/mode等信息,限於篇幅過長,讀者可自行分析
parameterMappings.add(buildParameterMapping(content));
// 將`#{}`替換為?,即一般包裝成`select * form test where name=? and age=?`預表達式語句
return "?";
}
上述主要通過
ParameterMappingTokenHandler
類來完成對#{}
字符串的解析,其中的映射信息則保存至BoundSql的parameterMappings屬性中
總結
BoundSql語句的解析主要是通過對
#{}
字符的解析,將其替換成?。最后均包裝成預表達式供PrepareStatement
調用執行
#{}
中的key屬性以及相應的參數映射,比如javaType、jdbcType等信息均保存至BoundSql的parameterMappings屬性中供最后的預表達式對象PrepareStatement
賦值使用