mybatis原始碼分析(sqlSessionFactory生成過程)
1. mybatis框架在現在各個IT公司的使用不用多說,這幾天看了mybatis的一些原始碼,趕緊做個筆記.
2. 看原始碼從一個demo引入如下:
public class TestApp {
private static SqlSessionFactory sqlSessionFactory;
static {
InputStream inputStream;
String resource = "mybatis-config.xml";
try {
//獲取全域性配置檔案的資料流
inputStream = Resources.getResourceAsStream(resource);
//獲取sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
}複製程式碼
如上程式碼獲取SQLSessionFactory例項物件,下來進入SqlSessionFactoryBuilder類中看其如何通過build方法建立SQLsessionFactory物件的:
//外部呼叫的SQLSessionFactoryBuilder的build方法:
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
複製程式碼
上面的方法呼叫下面的三個引數的build方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//這裡建立的是mybatis全域性配置檔案的解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//解析器parser呼叫parse()方法對全域性配置檔案進行解析,返回一個Configuration物件,這個物件包含了mybatis全域性配置檔案中以及mapper檔案中的所有配置資訊
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}複製程式碼
接著呼叫SqlSessionFactoryBuilder中的build(x)方法,建立一個defaultSQLSessionFactory物件返回給使用者
// 入參是XMLConfigBuilder解析器解析後返回的Configuration物件,這個方法中建立一個defaultSqlSessonFactory物件給使用者,其中包含一個Configuration物件屬性
// 所以我們獲取的SQLSessionFactory物件中就包含了專案中mybatis配置的所有資訊
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}複製程式碼
上面的幾個步驟中建立了sqlSessionFactory物件,下來我們看mybatis如何解析配置檔案以及獲取Configuration物件:
3. mybaties解析配置檔案
進入上面提到的XMLConfigBuilder parser.parse()方法中.
//類XMLConfigBuilder
//parsed第一次解析配置檔案時預設為FALSE,下面設定為true,也就是配置檔案只解析一次
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//這裡解析開始解析配置檔案,這裡的parser.evelNode("/configuration")是獲取元素"configuration"下面的所有資訊,也就是主配置檔案的根節點下的所有配置
parseConfiguration(parser.evalNode("/configuration"));
//將配置檔案解析完後,也就是對configuration物件組裝完成後,返回組裝(設值)後的configuration例項物件
return configuration;
}複製程式碼
進入parseConfiguration()方法中,傳入的是配置檔案的主要資訊
複製程式碼
private void parseConfiguration(XNode root) {
try {
//這裡解析配置檔案中配置的properties元素,其作用是如果存在properties元素的配置,則解析配置的property屬性,存放到configuration.setVariables(defaults)中去,具體看原始碼這裡不詳述;
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
//解析別名
typeAliasesElement(root.evalNode("typeAliases"));
//解析外掛
pluginElement(root.evalNode("plugins"));
//這兩個暫時不懂
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析全域性配置屬性settings
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析型別轉換器
typeHandlerElement(root.evalNode("typeHandlers"));
//以上的解析結果都存放到了configuration的相關屬性中,下來這個解析配置的mappers節點以及其對應的mapper檔案,我們主要看下這個.
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
複製程式碼
接入方法 mapperElement(xxxx)進行mapper檔案的解析,其引數是通過root.evalNode("mappers");解析之後的<mappers>xxxxx</mappers>節點
private void mapperElement(XNode parent) throws Exception {
//判斷是否配置了mapper檔案,如果沒有就直接退出
if (parent != null) {
//這裡獲取所有的mappers的子元素,然後遍歷挨個解析
for (XNode child : parent.getChildren()) {
//解析以包方式進行的配置
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//這裡是解析以檔案方式配置的mapper,檔案配置方式包括三種:resource,url,mapperClass;今天我們主要看以resource方式配置的解析
//獲取檔案配置路徑
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//此種方式配置: <mapper resource="com/wfl/aries/mapper/UserMapper.xml"/>
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//獲取mapper配置檔案的流
InputStream inputStream = Resources.getResourceAsStream(resource);
//建立mapper解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析mapper檔案開始
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}複製程式碼
進入mapper解析器
//類XMLMapperBuilder中
//解析mapper的方法
public void parse() {
//判斷是否已經解析和載入過了,如果沒有則進行解析
if (!configuration.isResourceLoaded(resource)) {
//解析mapper傳入的引數是mapper檔案的根節點
configurationElement(parser.evalNode("/mapper"));
//將該mapper的namespace新增到configuration的物件的一個set集合中去
configuration.addLoadedResource(resource);
//後續的mapper檔案配置屬性和configuration物件關聯
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}複製程式碼
具體的解析mapper檔案的方法
private void configurationElement(XNode context) {
try {
//獲取名稱空間
String namespace = context.getStringAttribute("namespace");
//如果名稱空間為空則丟擲異常
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//處理引數對映配置
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//處理結果對映配置
resultMapElements(context.evalNodes("/mapper/resultMap"));
//處理sql片段
sqlElement(context.evalNodes("/mapper/sql"));
//解析statment
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}複製程式碼
解析statement的方法
//入參是一個statement的列表,也就是mapper中配置的所有的startement檔案
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//被上面的方法呼叫解析statement
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
//迴圈變數解析每一個statement
for (XNode context : list) {
//建立一個statement解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析單個statement
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
//解析statement讀取配置檔案中配置的statement的相關屬性,並取值之後建立一個mapperdStatement
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("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);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
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);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}複製程式碼
建立一個mapperdStatement並新增到configuration的mappedStatements的map集合中.
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 = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder);
setStatementParameterMap(parameterMap, parameterType, statementBuilder);
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}複製程式碼