一、原始碼下載

1、手動編譯原始碼

為了方便在看原始碼的過程中能夠方便的添加註釋,可以從官網下載原始碼編譯生成對應的Jar包,然後上傳到本地maven倉庫,再引用這個Jar。

首先需要編譯打包parent專案,我是用idea進行打包編譯的

然後用同樣方法將mybaits專案也打個包。,mybaits我在打包時報錯了,後面用下面命令打包成功了

mvn install -DskipTests=true -Dmaven.test.skip=true -Dlicense.skip=true

2、關聯原始碼

原始碼編譯好後,隨便搞個專案把編譯後的包導進去就行了,記得編譯mybaits包時把版本號換了,要不會跟官網包衝突

然後修改配置 Project Structure —— Libries —— Maven: org.mybatis:mybatis:3.5.4-snapshot ——在原來的Sources上面點+(加號) —— 選擇到下載的原始碼路徑

二、MyBatis原始碼分析

1.三層劃分介紹

Mybatis的整體框架分為三層,分別是基礎支援層、核心處理層、和介面層。如下圖

MyBatis的主要工作流程圖

1.SqlSessionFactoryBuilder (構造器):使用Builder模式根據mybatis-config.xml配置或者程式碼來生成SqISessionFactory。

2.SqlSessionFactory (工廠介面):使用工廠模式生成SqlSession。

3.SqlSession (會話): 一個既可以傳送 SQL 執行返回結果,也可以獲取Mapper的介面。

4.SQL Mapper (對映器): 它由一個Java介面和XML檔案(或註解)構成,需要給出對應的SQL和對映規則,它負責傳送SQL去執行,並返回結果。

5.Executor(執行器)

1.1 介面層

首先介面層是我們打交道最多的。核心物件是SqlSession,它是上層應用和MyBatis打交道的橋樑,SqlSession上定義了非常多的對資料庫的操作方法。介面層在接收到呼叫請求的時候,會呼叫核心處理層的相應模組來完成具體的資料庫操作。

1.2 核心處理層

接下來是核心處理層。既然叫核心處理層,也就是跟資料庫操作相關的動作都是在這一層完成的。核心處理層主要做了這幾件事:
把介面中傳入的引數解析並且對映成JDBC型別;

  • 解析xml檔案中的SQL語句,包括插入引數,和動態SQL的生成;
  • 執行SQL語句;
  • 處理結果集,並對映成Java物件。

外掛也屬於核心層,這是由它的工作方式和攔截的物件決定的。

1.3 基礎支援層

最後一個就是基礎支援層。基礎支援層主要是一些抽取出來的通用的功能(實現複用),用來支援核心處理層的功能。比如資料來源、快取、日誌、xml解析、反射、IO、事務等等這些功能

2. 核心流程

 @Test
public void test1() throws Exception{
// 1.獲取配置檔案
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.載入解析配置檔案並獲取SqlSessionFactory物件
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根據SqlSessionFactory物件獲取SqlSession物件
SqlSession sqlSession = factory.openSession();
// 4.通過SqlSession中提供的 API方法來操作資料庫
List<User> list = sqlSession.selectList("com.ghy.mapper.UserMapper.selectUserList");
for (User user : list) {
System.out.println(user);
}
// 5.關閉會話
sqlSession.close();
}

前面寫的demo中其實就已經實現了一個比較複雜的查詢功能,下面要做的事就是通過這五個步驟分析下MyBatis的執行原理

2.1 核心物件的生命週期

2.1.1 SqlSessionFactoryBuiler

首先是SqlSessionFactoryBuiler。它是用來構建SqlSessionFactory的,而SqlSessionFactory只需要一個,所以只要構建了這一個SqlSessionFactory,它的使命就完成了,也就沒有存在的意義了。所以它的生命週期只存在於方法的區域性。

2.1.2 SqlSessionFactory

SqlSessionFactory是用來建立SqlSession的,每次應用程式訪問資料庫,都需要建立一個會話。因為我們一直有建立會話的需要,所以SqlSessionFactory應該存在於應用的整個生命週期中(作用域是應用作用域)。建立SqlSession只需要一個例項來做這件事就行了,否則會產生很多的混亂,和浪費資源。所以我們要採用單例模式。

2.1.3 SqlSession

SqlSession是一個會話,因為它不是執行緒安全的,不能線上程間共享。所以我們在請求開始的時候建立一個SqlSession物件,在請求結束或者說方法執行完畢的時候要及時關閉它(一次請求或者操作中)。

2.1.4 Mapper

Mapper(實際上是一個代理物件)是從SqlSession中獲取的。

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

它的作用是傳送SQL來操作資料庫的資料。它應該在一個SqlSession事務方法之內。

2.2 SqlSessionFactory

首先來看下SqlSessionFactory物件的獲取

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

2.2.1 SqlSessionFactoryBuilder

首先new了一個SqlSessionFactoryBuilder,這是建造者模式的運用(建造者模式用來建立複雜物件,而不需要關注內部細節,是一種封裝的體現)。MyBatis中很多地方用到了建造者模式(名字以Builder結尾的類還有9個)。

SqlSessionFactoryBuilder中用來建立SqlSessionFactory物件的方法是build(),build()方法有9個過載,可以用不同的方式來建立SqlSessionFactory物件。SqlSessionFactory物件預設是單例的。

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用於解析 mybatis-config.xml,同時建立了 Configuration 物件 >>
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最終返回一個 DefaultSqlSessionFactory >>
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.
}
}
}

在build方法中首先是建立了一個XMLConfigBuilder物件,XMLConfigBuilder是抽象類BaseBuilder的一個子類,專門用來解析全域性配置檔案,針對不同的構建目標還有其他的一些子類(關聯到原始碼路徑),比如:

  • XMLMapperBuilder:解析Mapper對映器
  • XMLStatementBuilder:解析增刪改查標籤
  • XMLScriptBuilder:解析動態SQL

然後是執行了

build(parser.parse());

構建的程式碼,parser.parse()方法返回的是一個Configuration物件,build方法的如下

  public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在這兒我們可以看到SessionFactory最終實現是DefaultSqlSessionFactory物件。

2.2.2 XMLConfigBuilder

然後我們再來看下XMLConfigBuilder初始化的時候做了哪些操作

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// EntityResolver的實現類是XMLMapperEntityResolver 來完成配置檔案的校驗,根據對應的DTD檔案來實現
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

再去進入過載的構造方法中

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration()); // 完成了Configuration的初始化
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props); // 設定對應的Properties屬性
this.parsed = false; // 設定 是否解析的標誌為 false
this.environment = environment; // 初始化environment
this.parser = parser; // 初始化 解析器
}

2.2.3 Configuration

然後可以看下Configuration初始化做了什麼操作

 public Configuration() {
// 為型別註冊別名
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}

完成了類型別名的註冊工作,通過上面的分析可以看到XMLConfigBuilder完成了XML檔案的解析對應XPathParser和Configuration物件的初始化操作,然後再來看下parse方法到底是如何解析配置檔案的

2.2.4 parse解析

parser.parse()

進入具體的解析方法

  public Configuration parse() {
//檢查是否已經解析過了
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// XPathParser,dom 和 SAX 都有用到 >>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

parseConfiguration方法

private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 對於全域性配置檔案各種標籤的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 標籤
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 讀取檔案
loadCustomVfs(settings);
// 日誌設定
loadCustomLogImpl(settings);
// 類型別名
typeAliasesElement(root.evalNode("typeAliases"));
// 外掛
pluginElement(root.evalNode("plugins"));
// 用於建立物件
objectFactoryElement(root.evalNode("objectFactory"));
// 用於對物件進行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 子標籤賦值,預設值就是在這裡提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 建立了資料來源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper對映器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.2.4.1 全域性配置檔案解析

properties解析

 private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 建立了一個 Properties 物件,後面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
// url 和 resource 不能同時存在
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 載入resource或者url屬性中指定的 properties 檔案
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
// 和 Configuration中的 variables 屬性合併
defaults.putAll(vars);
}
// 更新對應的屬性資訊
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}

第一個是解析<properties>標籤,讀取我們引入的外部配置檔案,例如db.properties。這裡面又有兩種型別,一種是放在resource目錄下的,是相對路徑,一種是寫的絕對路徑的(url)。解析的最終結果就是我們會把所有的配置資訊放到名為defaults的Properties物件裡面(Hashtable物件,KV儲存),最後把XPathParser和Configuration的Properties屬性都設定成我們填充後的Properties物件。

settings解析

  private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 獲取settings節點下的所有的子節點
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
//
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}

getChildrenAsProperties方法就是具體的解析了

  public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
// 獲取對應的name和value屬性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}

loadCustomVfs(settings)方法

loadCustomVfs是獲取Vitual File System的自定義實現類,比如要讀取本地檔案,或者FTP遠端檔案的時候,就可以用到自定義的VFS類。
根據<settings>標籤裡面的<vfsImpl>標籤,生成了一個抽象類VFS的子類,在MyBatis中有JBoss6VFS和DefaultVFS兩個實現,在io包中。

  private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}

最後賦值到Configuration中。

loadCustomLogImpl(settings)方法

loadCustomLogImpl是根據<logImpl>標籤獲取日誌的實現類,可以用到很多的日誌的方案,包括LOG4J,LOG4J2,SLF4J等等,在logging包中。

  private void loadCustomLogImpl(Properties props) {
// 獲取 logImpl設定的 日誌 型別
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
// 設定日誌
configuration.setLogImpl(logImpl);
}

typeAliases解析

這一步是類型別名的解析

  private void typeAliasesElement(XNode parent) {
// 放入 TypeAliasRegistry
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 掃描 @Alias 註解使用
typeAliasRegistry.registerAlias(clazz);
} else {
// 直接註冊
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}

plugins解析

外掛標籤的解析

  private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 獲取<plugin> 節點的 interceptor 屬性的值
String interceptor = child.getStringAttribute("interceptor");
// 獲取<plugin> 下的所有的properties子節點
Properties properties = child.getChildrenAsProperties();
// 獲取 Interceptor 物件
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 設定 interceptor的 屬性
interceptorInstance.setProperties(properties);
// Configuration中記錄 Interceptor
configuration.addInterceptor(interceptorInstance);
}
}
}

objectFactory,objectWrapperFactory及reflectorFactory解析

  private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
// 獲取<objectFactory> 節點的 type 屬性
String type = context.getStringAttribute("type");
// 獲取 <objectFactory> 節點下的配置資訊
Properties properties = context.getChildrenAsProperties();
// 獲取ObjectFactory 物件的物件 通過反射方式
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
// ObjectFactory 和 對應的屬性資訊關聯
factory.setProperties(properties);
// 將建立的ObjectFactory物件繫結到Configuration中
configuration.setObjectFactory(factory);
}
} private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
} private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setReflectorFactory(factory);
}
}

ObjectFactory用來建立返回的物件。

ObjectWrapperFactory用來對物件做特殊的處理。比如:select沒有寫別名,查詢返回的是一個Map,可以在自定義的objectWrapperFactory中把下劃線命名變成駝峰命名。
ReflectorFactory是反射的工具箱,對反射的操作進行了封裝(官網和文件沒有這個物件的描述)。

以上四個物件,都是用resolveClass建立的。

settingsElement(settings)方法

這裡就是對<settings>標籤裡面所有子標籤的處理了,前面已經把子標籤全部轉換成了Properties物件,所以在這裡處理Properties物件就可以了。settings二級標籤中一共26個配置,比如二級快取、延遲載入、預設執行器型別等等。需要注意的是,之前提到的所有的預設值,都是在這裡賦值的。如果說後面我們不知道這個屬性的值是什麼,也可以到這一步來確認一下。所有的值,都會賦值到Configuration的屬性裡面去。

private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

environments解析

這一步是解析<environments>標籤。前面講過,一個environment就是對應一個數據源,所以在這裡會根據配置的<transactionManager>建立一個事務工廠,根據<dataSource>標籤建立一個數據源,最後把這兩個物件設定成Environment物件的屬性,放到Configuration裡面。

  private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 事務工廠
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 資料來源工廠(例如 DruidDataSourceFactory )
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 資料來源
DataSource dataSource = dsFactory.getDataSource();
// 包含了 事務工廠和資料來源的 Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 放入 Configuration
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}

databaseIdProviderElement()

解析databaseIdProvider標籤,生成DatabaseIdProvider物件(用來支援不同廠商的資料庫)。
typeHandlerElement()

跟TypeAlias一樣,TypeHandler有兩種配置方式,一種是單獨配置一個類,一種是指定一個package。最後我們得到的是JavaType和JdbcType,以及用來做相互對映的TypeHandler之間的對映關係,存放在TypeHandlerRegistry物件裡面。

 private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}

mapper解析

最後就是<mappers>標籤的解析。根據全域性配置檔案中不同的註冊方式,用不同的方式掃描,但最終都是做了兩件事情,對於語句的註冊和介面的註冊。

掃描型別 含義
resource 相對路徑
url 絕對路徑
package
class 單個介面
  private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 不同的定義方式的掃描,最終都是呼叫 addMapper()方法(新增到 MapperRegistry)。這個方法和 getMapper() 對應
// package 包
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) {
// resource 相對路徑
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析 Mapper.xml,總體上做了兩件事情 >>
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// url 絕對路徑
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 單個介面
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.");
}
}
}
}
}

然後開始進入具體的配置檔案的解析操作

2.2.4.2 對映檔案的解析
首先進入parse方法

  public void parse() {
// 總體上做了兩件事情,對於語句的註冊和介面的註冊
if (!configuration.isResourceLoaded(resource)) {
// 1、具體增刪改查標籤的解析。
// 一個標籤一個MappedStatement。 >>
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 2、把namespace(介面型別)和工廠類繫結起來,放到一個map。
// 一個namespace 一個 MapperProxyFactory >>
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
  • configurationElement()——解析所有的子標籤,最終獲得MappedStatement物件。
  • bindMapperForNamespace()——把namespace(介面型別)和工廠類MapperProxyFactory繫結起來。

configurationElement方法

configurationElement是對Mapper.xml中所有具體的標籤的解析,包括namespace、cache、parameterMap、resultMap、sql和select|insert|update|delete。

 private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 新增快取物件
cacheRefElement(context.evalNode("cache-ref"));
// 解析 cache 屬性,新增快取物件
cacheElement(context.evalNode("cache"));
// 建立 ParameterMapping 物件
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 建立 List<ResultMapping>
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析可以複用的SQL
sqlElement(context.evalNodes("/mapper/sql"));
// 解析增刪改查標籤,得到 MappedStatement >>
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);
}
}

在buildStatementFromContext()方法中,建立了用來解析增刪改查標籤的XMLStatementBuilder,並且把建立的MappedStatement新增到mappedStatements中。
進入buildStatementFromContext(context.evalNodes("select|insert|update|delete"));方法

 private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
//多資料來源的話進這個方法
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 解析 Statement >>
buildStatementFromContext(list, null);
}
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 用來解析增刪改查標籤的 XMLStatementBuilder;XMLStatementBuilder是構造物件
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析 Statement,新增 MappedStatement 物件 >>解析單個增刪改查的節點
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

進入parseStatementNode()方法,看下是怎麼解析的

/**
* <select id="queryById" resultType="" .....></select>
*/
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
} 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()); String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} 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"); // >> 關鍵的一步: MappedStatement 的建立
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

看他關鍵程式碼MappedStatement 的建立,點進去看下

 //下面括號裡傳的參其實可以把它看起標籤裡的屬性資訊
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 添加了 MappedStatement >>對就的就是一個CRUD標籤
configuration.addMappedStatement(statement);
return statement;
}

看到這裡程式碼就可以後退到前面如下位置

看到這裡其實整個對映檔案的程式碼就看完了,程式碼再次後退到如下位置看下bindMapperForNamespace();方法做了什麼

public void parse() {
// 總體上做了兩件事情,對於語句的註冊和介面的註冊
if (!configuration.isResourceLoaded(resource)) {
// 1、具體增刪改查標籤的解析。
// 一個標籤一個MappedStatement。 >>
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 2、把namespace(介面型別)和工廠類繫結起來,放到一個map。
// 一個namespace 一個 MapperProxyFactory >>
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

bindMapperForNamespace方法

  private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 根據名稱空間載入對應的介面型別
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// 判斷 在MapperRegistry中是否註冊的有當前型別的 MapperProxyFactory物件
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 新增到 MapperRegistry,本質是一個 map,裡面也有 Configuration >>
configuration.addMapper(boundType);
}
}
}
}

通過原始碼分析發現主要是是呼叫了addMapper()。addMapper()方法中,把介面型別註冊到MapperRegistry中:實際上是為介面建立一個對應的MapperProxyFactory(用於為這個type提供工廠類,建立MapperProxy)。

 public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 檢測 type 是否為介面
if (hasMapper(type)) { // 檢測是否已經加裝過該介面
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// !Map<Class<?>, MapperProxyFactory<?>> 存放的是介面型別,和對應的工廠類的關係
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. // 註冊了介面之後,根據介面,開始解析所有方法上的註解,例如 @Select >>
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

同樣的再進入parse方法中檢視

 public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 先判斷 Mapper.xml 有沒有解析,沒有的話先解析 Mapper.xml(例如定義 package 方式)
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 處理 @CacheNamespace
parseCache();
// 處理 @CacheNamespaceRef
parseCacheRef();
// 獲取所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// 解析方法上的註解,新增到 MappedStatement 集合中 >>
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}

總結:

  • 主要完成了config配置檔案、Mapper檔案、Mapper介面中註解的解析。
  • 得到了一個最重要的物件Configuration,這裡面存放了全部的配置資訊,它在屬性裡面還有各種各樣的容器。
  • 最後,返回了一個DefaultSqlSessionFactory,裡面持有了Configuration的例項。

2.3 SqlSession

程式每一次操作資料庫,都需要建立一個會話,我們用openSession()方法來建立。接下來看看SqlSession建立過程中做了哪些操作

SqlSession sqlSession = factory.openSession();

通過前面建立的DefaultSqlSessionFactory的openSession方法來建立

  @Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

首先會獲取預設的執行器型別。預設的是simple

  public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

繼續往下

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 獲取事務工廠
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 建立事務
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根據事務工廠和預設的執行器型別,建立執行器 >>
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

在解析environment標籤的時候有建立TransactionFactory物件,根據事務工廠和預設的執行器型別,建立執行器

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 預設 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二級快取開關,settings 中的 cacheEnabled 預設是 true
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入外掛的邏輯,至此,四大物件已經全部攔截完畢
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

最後返回的是一個DefaultSqlSession物件

在這個DefaultSqlSession物件中包括了Configuration和Executor物件

總結:建立會話的過程,我們獲得了一個DefaultSqlSession,裡面包含了一個Executor,Executor是SQL的實際執行物件。

2.4 Mapper代理物件

接下來看下通過getMapper方法獲取對應的介面的代理物件的實現原理

       // 4.通過SqlSession中提供的 API方法來操作資料庫
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

進入DefaultSqlSession中檢視

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// mapperRegistry中註冊的有Mapper的相關資訊 在解析對映檔案時 呼叫過addMapper方法
return mapperRegistry.getMapper(type, sqlSession);
}

進入getMapper方法

  /**
* 獲取Mapper介面對應的代理物件
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 獲取Mapper介面對應的 MapperProxyFactory 物件
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

進入newInstance方法

  public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

繼續

  /**
* 建立實現了 mapperInterface 介面的代理物件
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:類載入器:2:被代理類實現的介面、3:實現了 InvocationHandler 的觸發管理類
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

最終在程式碼中發現代理物件是通過JDK動態代理建立,返回的代理物件。而且裡面也傳遞了一個實現了InvocationHandler介面的觸發管理類。

總結:獲得Mapper物件的過程,實質上是獲取了一個JDK動態代理物件(型別是$ProxyN)。這個代理類會繼承Proxy類,實現被代理的介面,裡面持有了一個MapperProxy型別的觸發管理類。