在這里記錄一下自己學(xué)習(xí)mybatis源碼過程中的一些學(xué)習(xí)體會,文章內(nèi)容基于mybatis3.5.3-SNAPSHOT:
下面是mybatis一個測試用例中配置文件的截圖,配置文件詳情參考mybatis中文官網(wǎng):
1.事例
下面是mybatis測試用例中加載配置文件,并且運(yùn)行的過程,這篇文章主要記錄一下mybatis加載配置文件的過程
@BeforeAll
static void setUp() throws Exception {
// create a SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/permissions/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// populate in-memory database
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/submitted/permissions/CreateDB.sql");
}
從以上的實(shí)例代碼可以看到關(guān)于mybatis讀取默認(rèn)配置文件的過程,接下來就是詳細(xì)的看看整體的過程。
2.源碼分析
2.1創(chuàng)建SqlSessionFactory
SqlSession是mybatis的關(guān)鍵,這個接口包含了sql執(zhí)行,事務(wù),緩存等許多的方法。要獲取SqlSession就要先得到SqlSessionFactory。為了得到SqlSessionFactory就需要使用SqlSessionFactoryBuilder來解析配置文件,SqlSessionFactoryBuilder有多個build方法,基本一致,挑一個來看看。
public SqlSessionFactorybuild(Reader reader, String environment, Properties properties) {
try {
// 創(chuàng)建 XMLConfigBuilder 對象,底層使用的是jdk的XPath解析xml文件
XMLConfigBuilder parser =new XMLConfigBuilder(reader, environment, properties);
// 執(zhí)行 XML 解析
// 創(chuàng)建 DefaultSqlSessionFactory 對象
return build(parser.parse());
}catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
}finally {
ErrorContext.instance().reset();
try {
reader.close();
}catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
2.2 解析配置文件
下面我們來看看parser.parse():
public Configurationparse() {
// 判斷是否已經(jīng)加載過
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed =true;
// 解析configuration節(jié)點(diǎn)
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
最重要的是parseConfiguration方法:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 屬性
propertiesElement(root.evalNode("properties"));
// 設(shè)置,這是 MyBatis 中極為重要的調(diào)整設(shè)置,它們會改變 MyBatis 的運(yùn)行時行為
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加載自定義 VFS 實(shí)現(xiàn)類
loadCustomVfs(settings);
// 指定 MyBatis 所用日志的具體實(shí)現(xiàn),未指定時將自動查找
loadCustomLogImpl(settings);
// 類型別名,為 Java 類型設(shè)置一個短的名字
typeAliasesElement(root.evalNode("typeAliases"));
// 插件,在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用
pluginElement(root.evalNode("plugins"));
// 對象工廠,MyBatis 每次創(chuàng)建結(jié)果對象的新實(shí)例時,它都會使用一個對象工廠實(shí)例來完成
objectFactoryElement(root.evalNode("objectFactory"));
// 對象包裝工廠
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工廠
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 設(shè)置settings屬性到configuration中,沒有時設(shè)置默認(rèn)配置
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 環(huán)境配置
environmentsElement(root.evalNode("environments"));
// 數(shù)據(jù)庫廠商標(biāo)識
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 類型處理器,MyBatis 在預(yù)處理語句(PreparedStatement)中設(shè)置一個參數(shù)時,
// 還是從結(jié)果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成 Java 類型
typeHandlerElement(root.evalNode("typeHandlers"));
// SQL 映射語句
mapperElement(root.evalNode("mappers"));
}catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
大多數(shù)都是屬性的設(shè)置,最終所有的設(shè)置都會配置到XMLConfigBuilder以及父類BaseBuilder的屬性對象中,其中mapperElement方法是解析mapper.xml,即我們的mapper.xml文件或者*mapper.java接口(針對在java文件中通過注解創(chuàng)建sql和加上一些配置等)。
2.3 xml文件以及接口解析
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 如果是配置的package那就掃描包,針對已經(jīng)在方法上使用注解實(shí)現(xiàn)功能
<1>
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");
// 解析本地的xml文件
<2>
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
// 解析遠(yuǎn)程地址上的xml文件
<3>
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();
}
// 單個文件解析,也是針對已經(jīng)在方法上使用注解實(shí)現(xiàn)功能
<4>
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.");
}
}
}
}
}
<1>,<2>,<3>,<4>處的代碼,最終的解析方式都是解析解析xml的同時解析對應(yīng)的接口內(nèi)的方法,或者是先解析接口內(nèi)的方法再解析接口對應(yīng)的xml文件
configuration.addMappers,先來看下MapperRegistry.addMapper方法:
public <T> void addMapper(Class<T> type) {
// 判斷必須是接口
if (type.isInterface()) {
// 判斷是否解析過
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 用于判斷是否解析過
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
<1>
parser.parse();
loadCompleted = true;
} finally {
// 解析錯誤,留到后面解析
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
<1>處代碼最關(guān)鍵
public void parse() {
String resource = type.toString();
// 判斷是否加載過
if (!configuration.isResourceLoaded(resource)) {
// 加載對應(yīng)的*mapper.xml文件
<1>
loadXmlResource();
// 用于判斷是否加載
configuration.addLoadedResource(resource);
// 設(shè)置當(dāng)前命名空間,如果與當(dāng)前命名空間不一致,拋出錯誤
// 我理解可能是防止多線程下同時解析不同文件
assistant.setCurrentNamespace(type.getName());
// 解析@CacheNamespace,二級緩存相關(guān)
parseCache();
// 解析@CacheNamespaceRef,二級緩存相關(guān)
parseCacheRef();
Method[] methods = type.getMethods();
// 遍歷每個方法,解析其上的注解
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
<2>
parseStatement(method);
}
} catch (IncompleteElementException e) {
// 解析失敗,添加到 configuration 中
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 解析上面for循環(huán)解析失敗的方法
parsePendingMethods();
}
其中<1>處代碼是解析xml文件的,<2>處代碼是解析對應(yīng)的java接口
先來看看<1>處代碼是怎么找到并且解析xml文件的
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
// 判斷是否加載過
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 獲取當(dāng)前對應(yīng)的xml的路徑
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
// 獲取當(dāng)前模塊中的xml文件流對象
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
// 獲取不在當(dāng)前模塊,但是在對應(yīng)路徑下的xml文件
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
繼續(xù)來看看XMLMapperBuilder.parse()是如何解析xml文件的。
2.3.1解析xml
public void parse() {
// 如果沒有加載過
if (!configuration.isResourceLoaded(resource)) {
// 解析xml文件中的所有標(biāo)簽
<1>
configurationElement(parser.evalNode("/mapper"));
// 標(biāo)記該 Mapper 已經(jīng)加載過
configuration.addLoadedResource(resource);
// 解析對應(yīng)的*mapper.java文件,
// 解析xml或者java文件的時候都會去解析對應(yīng)的另外一個文件
// 在解析對應(yīng)的文件時都要判斷是否已經(jīng)解析過
bindMapperForNamespace();
}
// 解析待定的 <resultMap /> 節(jié)點(diǎn)
parsePendingResultMaps();
// 解析待定的 <cache-ref /> 節(jié)點(diǎn)
parsePendingCacheRefs();
// 解析待定的 SQL 語句的節(jié)點(diǎn)
parsePendingStatements();
}
重點(diǎn)看看<1>處的代碼,是如何解析整個xml文件中的所有節(jié)點(diǎn)的,最后面的3個方法是繼續(xù)嘗試解析前面解析xml文件時沒有解析成功的節(jié)點(diǎn)。
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");
}
// 設(shè)置 namespace 屬性
builderAssistant.setCurrentNamespace(namespace);
// 解析 <cache-ref> 節(jié)點(diǎn)
cacheRefElement(context.evalNode("cache-ref"));
// 解析 <cache> 節(jié)點(diǎn)
cacheElement(context.evalNode("cache"));
// 已廢棄!老式風(fēng)格的參數(shù)映射。內(nèi)聯(lián)參數(shù)是首選,這個元素可能在將來被移除,這里不會記錄。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// <1> 解析 <resultMap> 節(jié)點(diǎn)們,解析成resultMap對象保存在 configuration 中
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 <sql> 節(jié)點(diǎn)們,保存id和node對應(yīng)關(guān)系到 sqlFragments 中
sqlElement(context.evalNodes("/mapper/sql"));
// <2> 解析 <select> <insert> <update> <delete> 節(jié)點(diǎn)們
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
這里比較復(fù)雜的是<1>和<2>處的解析,別的比較簡單,可自行看一下。
2.3.1.1resultMapElements(context.evalNodes("/mapper/resultMap"))
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 獲取 type
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 替換別名,獲取 class 對象
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// 解析子節(jié)點(diǎn)
List<XNode> resultChildren = resultMapNode.getChildren();
// 解析所有的節(jié)點(diǎn)到 resultMappings 中
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 保存 resultMap 到 configuration 中
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
此處主要是解析<resultMap>節(jié)點(diǎn),構(gòu)造resultMap對象,并且保存到 configuration中
2.3.1.2buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
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) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
public void parseStatementNode() {
// 例如<select id="">
String id = context.getStringAttribute("id");
// 例如<select databaseId="">
String databaseId = context.getStringAttribute("databaseId");
// 判斷當(dāng)前節(jié)點(diǎn)是否已經(jīng)解析過 以及 判斷databaseId是否相等
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 獲取是什么類型的節(jié)點(diǎn) 如 :select
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
// 解析sql的 include 片段
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 獲取 parameterType 以及 別名轉(zhuǎn)換
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 國際化相關(guān)
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
// 在 configuration 中保存 KeyGenerator,用于自動生成主鍵
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
// 返回 currentNamespace + "." + id + "!selectKey"
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 如果沒有 <selectKey> 節(jié)點(diǎn),就查看 該節(jié)點(diǎn)是否配置了 useGeneratedKeys,
// 或者配置文件配置了 useGeneratedKeys 并且 該語句為 insert
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 如果是沒有動態(tài)標(biāo)簽的sql,#{}會解析為?,并且保存對應(yīng)的屬性
// 如果是動態(tài)標(biāo)簽的sql,按sql的順序拆分sql為 單個 sqlNode(ifSqlNode,forEachSqlNode等)或者是MixedSqlNode包含多個SqlNode
// 以及sql對應(yīng)的 prefix,subffix,prefixesToOverride等屬性
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// configuration 中保存 MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
最終新增的MappedStatement對象代表的是xml一個sql標(biāo)簽,包含這個sql的所有配置以及sql語句等屬性。
2.3.2 解析java接口
代碼入口在MapperAnnotationBuilder.parse()中的parseStatement(method)方法,這就是解析接口中的每個方法以及方法上的注解的。
void parseStatement(Method method) {
// 獲取非分頁的形參類型,多個參數(shù)用 ParamMap 表示
Class<?> parameterTypeClass = getParameterType(method);
// 注解式的動態(tài)sql
LanguageDriver languageDriver = getLanguageDriver(method);
// 獲取 sqlsourse 對象
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = configuration.getDefaultResultSetType();
SqlCommandType sqlCommandType = getSqlCommandType(method);
// 是否是 select sql 語句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 如果不是 select 默認(rèn)開始二級緩存
boolean flushCache = !isSelect;
// select 默認(rèn)開啟一級緩存
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
// 獲取生成自動主鍵的 keyGenerator
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// 構(gòu)造 mappedstatement configuration 中
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
大致的解析解析過程就是這樣的,總的來說就是解析所有的配置文件組成一個configuration對象,然后會調(diào)用SqlSessionFactory的build方法new 一個DefaultSqlSessionFactory對象,并且設(shè)置configuration,configuration幾乎包含了mybatis所有的屬性,貫穿幾乎所有的mybatis流程。