XMLMapperBuilder
用來解析XML中的SQL,MapperAnnotationBuilder
用來解析Mapper介面中的註解SQL。這裡只分析一下XMLMapperBuilder
。
以解析 resource 為例,即解析XML檔案。解析完XML檔案中的SQL,還會去解析該XML檔案對應的Mapper介面中的註解SQL。
// XMLConfigBuilder.java
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 獲取Mapper.xml的檔案流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 構造 XMLMapperBuilder 物件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析 Mapper.xml 檔案
mapperParser.parse();
}
// 省略......
}
}
}
}
parse
解析 Mapper XML 配置檔案
// XMLMapperBuilder.java
public void parse() {
// 判斷當前 Mapper 是否已經載入過
if (!configuration.isResourceLoaded(resource)) {
// 解析 <mapper /> 節點
configurationElement(parser.evalNode("/mapper"));
// 標記該 Mapper 已經載入過
configuration.addLoadedResource(resource);
// 繫結 Mapper.xml 對應的 Mapper 介面,並解析 Mapper 介面中的SQL註解
// 第一步:
// 該方法會將Mapper介面儲存到 Configuration 的 MapperRegistry 屬性裡面
// MapperRegistry 裡面有一個 Map<Class<?>, MapperProxyFactory<?>> knownMappers
// 最終透過 knownMappers.put(type, new MapperProxyFactory<T>(type)); 註冊Mapper代理
// 這裡為每一個Mapper介面生成了一個 MapperProxyFactory
// 第二步:解析 Mapper 介面中SQL語句並封裝成 MappedStatement 物件
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement
// XMLMapperBuilder.java
private void configurationElement(XNode context) {
try {
// 獲得 namespace 屬性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 設定 namespace 屬性
builderAssistant.setCurrentNamespace(namespace);
// 解析 <cache-ref /> 節點
cacheRefElement(context.evalNode("cache-ref"));
// 解析 <cache /> 節點,快取相關的一些屬性設定
cacheElement(context.evalNode("cache"));
// 已廢棄!老式風格的引數對映。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 <resultMap /> 節點
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 <sql /> 節點
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 <select /> <insert /> <update /> <delete />
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
主要分析buildStatementFromContext解析流程
buildStatementFromContext
解析
// XMLMapperBuilder.java
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) {
// 遍歷 <select />、<insert />、<update />、<delete /> 節點們,
// 依次建立 XMLStatementBuilder 物件,執行解析
for (XNode context : list) {
// 建立 XMLStatementBuilder 物件
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 執行解析
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
org.apache.ibatis.builder.xml.XMLStatementBuilder
,繼承 BaseBuilder 抽象類,Statement XML 配置構建器,主要負責解析 Statement 配置,即 <select />
、<insert />
、<update />
、<delete />
標籤。
建構函式
public class XMLStatementBuilder extends BaseBuilder {
// 儲存當前 Mapper.xml 的一些屬性資訊,如namespace
private final MapperBuilderAssistant builderAssistant;
// 當前 XML 節點,例如:<select />、<insert />、<update />、<delete /> 標籤
private final XNode context;
// 當前節點的 databaseId
private final String requiredDatabaseId;
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
super(configuration);
this.builderAssistant = builderAssistant;
this.context = context;
this.requiredDatabaseId = databaseId;
}
}
parseStatementNode
執行 Statement 解析
// XMLStatementBuilder
public void parseStatementNode() {
// 獲取當前節點的 id 屬性
String id = context.getStringAttribute("id");
// 獲取當前節點的 databaseId 屬性
String databaseId = context.getStringAttribute("databaseId");
// 判斷 databaseId 是否匹配
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 獲取當前節點的各種屬性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 獲得 SQL 對應的 SqlCommandType 列舉值
// select、insert、update、delete
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 判斷是否為 select 操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 獲取一些屬性
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 建立 XMLIncludeTransformer 物件,並替換 <include /> 標籤相關的內容
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析 <selectKey /> 標籤
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 建立 SqlSource,儲存了完整的SQL資訊
// 此時 <selectKey> 和 <include>標籤已經被解析過了並且被刪除了
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 獲得 KeyGenerator 物件
// (僅適用於 insert 和 update)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys
// 方法來取出由資料庫內部生成的主鍵
// (比如:像 MySQL 和 SQL Server 這樣的關係型資料庫管理系統的自動遞增欄位),預設值:false。
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 先從 configuration 中獲得 KeyGenerator 物件。如果存在,說明已經配置過了
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 根據標籤屬性,判斷是使用Jdbc3KeyGenerator 還是 NoKeyGenerator 物件
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 建立 MappedStatement 物件
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
解析完節點資訊後,最後會生成 MappedStatement 物件儲存到Configuration中。構建MappedStatement 物件的方法在MapperBuilderAssistant類裡面
構建 MappedStatement 物件
// MapperBuilderAssistant.java
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 獲得 id 編號,格式為 ${namespace}.${id}
id = applyCurrentNamespace(id, false);
// 判斷是否為 select 操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 建立 MappedStatement.Builder 物件
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 ,並設定到 MappedStatement.Builder 中
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 建立 MappedStatement 物件
MappedStatement statement = statementBuilder.build();
// 新增到 configuration 中的 Map<String, MappedStatement> mappedStatements
// key 為 id
// value 為 MappedStatement
configuration.addMappedStatement(statement);
return statement;
}
org.apache.ibatis.mapping.MappedStatement
儲存了每個 <select />
、<insert />
、<update />
、<delete />
節點的詳細資訊。
public final class MappedStatement {
// Mapper.xml配置檔名,如:UserMapper.xml
// Mapper介面檔名,如com/example/demo/UserMapper.java
private String resource;
// 全域性配置
private Configuration configuration;
// 節點的id屬性加名稱空間,如:com.example.demo.UserMapper.getUserByUserName
private String id;
private Integer fetchSize;
private Integer timeout;
// 操作SQL的物件的型別
// STATEMENT: 直接操作SQL,不進行預編譯
// PREPARED: 預處理引數,進行預編譯,獲取資料
// CALLABLE: 執行儲存過程
private StatementType statementType;
// 返回結果型別
// FORWARD_ONLY:結果集的遊標只能向下滾動
// SCROLL_INSENSITIVE:結果集的遊標可以上下移動,當資料庫變化時當前結果集不變
// SCROLL_SENSITIVE:返回可滾動的結果集,當資料庫變化時,當前結果集同步改變
private ResultSetType resultSetType;
// sql語句
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
// 返回結果型別
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
// sql語句的型別,如select、update、delete、insert
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
public BoundSql getBoundSql(Object parameterObject) {
// 獲得 BoundSql 物件
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 忽略這一步,因為 <parameterMap /> 已經廢棄
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;
}
}
org.apache.ibatis.mapping.SqlSource
,代表從 Mapper XML 或介面方法註解上讀取的一條 SQL 內容。程式碼如下:
public interface SqlSource {
// 根據使用者傳入的入參,返回 BoundSql 物件
BoundSql getBoundSql(Object parameterObject);
}
public class BoundSql {
// 儲存sql語句,例如:select * from user where username = ?
private final String sql;
// 儲存對#{}字串的解析結果
private final List<ParameterMapping> parameterMappings;
// 使用者傳遞進來的引數
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
}
BoundSql語句的解析主要是透過對#{}字元的解析,將其替換成?。最後均包裝成預表示式供PrepareStatement呼叫執行
#{}中的key屬性以及相應的引數對映,比如javaType、jdbcType等資訊均儲存至BoundSql的parameterMappings屬性中供最後的預表示式物件PrepareStatement賦值使用
MyBatis初始化完成後,每一個介面方法都會被封裝成了一個MappedStatement
物件,這個物件裡面包含了執行SQL語句的詳細資訊。
本作品採用《CC 協議》,轉載必須註明作者和本文連結