Mybatis啓動流程就是組裝Configuration對象的過程,在這其中主要就是初始化環境變量和初始化Mapper.xml的映射。
Mybatis採用了建造者模式來加載配置。
建造者模式
建造者模式(Builder Pattern):使用多個簡單的對象一步步構建成一個複雜的對象,這種設計模式屬於創建型模式,提供了一種創建對象的方式。
Builder:給出一個抽象接口,以規範產品對象的各個組成成分的建造。這個接口規定要實現複雜對象的哪些部分的創建,並不涉及具體的對象部件的創建。
ConcreteBuilder:實現Builder接口,針對不同的業務邏輯,具體化複雜對象的各部分創建。在建造過程完成後,提供產品的實例。
Director:調用具體建造者來創建複雜對象的各個部分,在指導者中不涉及具體產品的信息,只負責保證對象各部分完整創建或按某種順序創建。
Product:要創建的複雜對象。
Mybatis建造者
核心類
Configuration:Mybatis啓動初始化的核心就是將XML配置文件的信息加載到Configuration對象中。Configuration是單例的,生命週期是應用級的。
protected Environment environment;
/* 是否啓用行內嵌套語句**/
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
/* 是否啓用數據組A_column自動映射到Java類中的駝峯命名的屬性**/
protected boolean mapUnderscoreToCamelCase;
/*當對象使用延遲加載時 屬性的加載取決於能被引用到的那些延遲屬性,否則,按需加載(需要的是時候纔去加載)**/
protected boolean aggressiveLazyLoading;
/*是否允許單條sql 返回多個數據集 (取決於驅動的兼容性) default:true **/
protected boolean multipleResultSetsEnabled = true;
/*-允許JDBC 生成主鍵。需要驅動器支持。如果設爲了true,這個設置將強制使用被生成的主鍵,有一些驅動器不兼容不過仍然可以執行。 default:false**/
protected boolean useGeneratedKeys;
/* 使用列標籤代替列名。不同的驅動在這方面會有不同的表現, 具體可參考相關驅動文檔或通過測試這兩種不同的模式來觀察所用驅動的結果。**/
protected boolean useColumnLabel = true;
/*配置全局性的cache開關,默認爲true**/
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
/* 日誌打印所有的前綴 **/
protected String logPrefix;
/* 指定 MyBatis 所用日誌的具體實現,未指定時將自動查找**/
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
/* 設置本地緩存範圍,session:就會有數據的共享,statement:語句範圍,這樣不會有數據的共享**/
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
/* 設置但JDBC類型爲空時,某些驅動程序 要指定值**/
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
/* 設置觸發延遲加載的方法**/
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
/* 設置驅動等待數據響應超時數**/
protected Integer defaultStatementTimeout;
/* 設置驅動返回結果數的大小**/
protected Integer defaultFetchSize;
/* 執行類型,有simple、resue及batch**/
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
/*指定 MyBatis 應如何自動映射列到字段或屬性*/
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
/*MyBatis每次創建結果對象的新實例時,它都會使用對象工廠(ObjectFactory)去構建POJO*/
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
/*延遲加載的全局開關*/
protected boolean lazyLoadingEnabled = false;
/*指定 Mybatis 創建具有延遲加載能力的對象所用到的代理工具*/
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
/*插件集合*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
/*TypeHandler註冊中心*/
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
/*TypeAlias註冊中心*/
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//-------------------------------------------------------------
/*mapper接口的動態代理註冊中心*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/*mapper文件中增刪改查操作的註冊中心*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
/*mapper文件中配置cache節點的 二級緩存*/
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
/*mapper文件中配置的所有resultMap對象 key爲命名空間+ID*/
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
/*mapper文件中配置KeyGenerator的insert和update節點,key爲命名空間+ID*/
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
/*加載到的所有*mapper.xml文件*/
protected final Set<String> loadedResources = new HashSet<>();
/*mapper文件中配置的sql元素,key爲命名空間+ID*/
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
MapperRegistry:mapper接口動態代理工廠類的註冊中心。Mybatis中,通過mapperProxy實現InvocationHandler接口,MapperProxyFactory用於生成動態代理的實例對象。
ResultMap:用於解析mapper.xml文件中的resultMap節點,使用ResultMapping來封裝id,result等子元素。
MappedStatement:用於儲存mapper.xml文件中的select、insert、update、delete節點,同時還包含這些節點的其他重要屬性。
SqlSource:mapper.xml文件中的sql語句會被解析成SqlSource對象,經過解析SqlSource包含的語句最終僅僅包含?佔位符,可以直接提交給數據庫執行。
啓動流程
SqlSessionFactoryBuilder讀取配置文件
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//讀取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());//解析配置文件得到configuration對象,並返回SqlSessionFactory
} 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.
}
}
}
XMLConfigBuilder解析config.xml
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//解析<properties>節點
propertiesElement(root.evalNode("properties"));
//解析<settings>節點
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析<typeAliases>節點
typeAliasesElement(root.evalNode("typeAliases"));
//解析<plugins>節點
pluginElement(root.evalNode("plugins"));
//解析<objectFactory>節點
objectFactoryElement(root.evalNode("objectFactory"));
//解析<objectWrapperFactory>節點
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析<reflectorFactory>節點
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);//將settings填充到configuration
// read it after objectFactory and objectWrapperFactory issue #631
//解析<environments>節點
environmentsElement(root.evalNode("environments"));
//解析<databaseIdProvider>節點
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析<typeHandlers>節點
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>節點
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLMapperBuilder解析mapper.xml
public void parse() {
//判斷是否已經加載該配置文件
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));//處理mapper節點
configuration.addLoadedResource(resource);//將mapper文件添加到configuration.loadedResources中
bindMapperForNamespace();//註冊mapper接口
}
//處理解析失敗的ResultMap節點
parsePendingResultMaps();
//處理解析失敗的CacheRef節點
parsePendingCacheRefs();
//處理解析失敗的Sql語句節點
parsePendingStatements();
}
public XNode getSqlFragment(String refid) {
return sqlFragments.get(refid);
}
private void configurationElement(XNode context) {
try {
//獲取mapper節點的namespace屬性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//設置builderAssistant的namespace屬性
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref節點
cacheRefElement(context.evalNode("cache-ref"));
//重點分析 :解析cache節點----------------1-------------------
cacheElement(context.evalNode("cache"));
//解析parameterMap節點(已廢棄)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//重點分析 :解析resultMap節點(基於數據結果去理解)----------------2-------------------
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql節點
sqlElement(context.evalNodes("/mapper/sql"));
//重點分析 :解析select、insert、update、delete節點 ----------------3-------------------
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);
}
}
//解析select、insert、update、delete節點
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//處理所有的sql語句節點並註冊至configuration對象
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//創建XMLStatementBuilder 專門用於解析sql語句節點
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析sql語句節點
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder解析sql節點
public void parseStatementNode() {
//獲取sql節點的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/*獲取sql節點的各種屬性*/
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(INSERT, UPDATE, DELETE, 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());
// Parse selectKey after includes and remove them.
//在解析sql語句之前,處理<selectKey>子節點,並在xml節點中刪除
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//解析sql語句是解析mapper.xml的核心,實例化sqlSource,使用sqlSource封裝sql語句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");//獲取resultSets屬性
String keyProperty = context.getStringAttribute("keyProperty");//獲取主鍵信息keyProperty
String keyColumn = context.getStringAttribute("keyColumn");///獲取主鍵信息keyColumn
//根據<selectKey>獲取對應的SelectKeyGenerator的id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//獲取keyGenerator對象,如果是insert類型的sql語句,會使用KeyGenerator接口獲取數據庫生產的id;
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//通過builderAssistant實例化MappedStatement,並註冊至configuration對象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
}
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
removeSelectKeyNodes(selectKeyNodes);
}
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
for (XNode nodeToHandle : list) {
String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
String databaseId = nodeToHandle.getStringAttribute("databaseId");
if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
}
}
}
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
String resultType = nodeToHandle.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
//defaults
boolean useCache = false;
boolean resultOrdered = false;
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
Integer fetchSize = null;
Integer timeout = null;
boolean flushCache = false;
String parameterMap = null;
String resultMap = null;
ResultSetType resultSetTypeEnum = null;
SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
SqlCommandType sqlCommandType = SqlCommandType.SELECT;
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
id = builderAssistant.applyCurrentNamespace(id, false);
MappedStatement keyStatement = configuration.getMappedStatement(id, false);
configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}
private void removeSelectKeyNodes(List<XNode> selectKeyNodes) {
for (XNode nodeToHandle : selectKeyNodes) {
nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
}
}
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
if (databaseId != null) {
return false;
}
// skip this statement if there is a previous one with a not null databaseId
id = builderAssistant.applyCurrentNamespace(id, false);
if (this.configuration.hasStatement(id, false)) {
MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
if (previous.getDatabaseId() != null) {
return false;
}
}
}
return true;
}
MapperBuilderAssistant輔助XMLMapperBuilder解析mapper.xml文件,完善屬性信息,並註冊到configuration對象
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)
.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 statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}