[TOC]html
搭建源碼環境
在這裏我提一下,在早期Mybatis版本中,Dao開發方式都是有Mapper接口和其實現類的,實現類是須要咱們本身編寫的,後來Mybatis使用JDK動態代理針對Mapper接口作了代理,替咱們實現了實現類; 可是其底層也是使用了Mapper接口的實現類,不可能說只有一個便可就能和JDBC進行通信 ! 其基礎環境搭建可參照官方教程 http://www.mybatis.org/mybatis-3/zh/getting-started.htmljava
POM依賴
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency>
測試SQL
CREATE TABLE `user` ( `id` int(10) NOT NULL AUTO_INCREMENT, `username` varchar(10) NOT NULL, `password` varchar(52) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1
Mybatis全局配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value="jimisun"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
UserMapper接口
public interface UserMapper { User selectUser(Integer id); }
UserMapper配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="user"> <select id="selectUser" resultType="com.jimisun.mybatis.domain.User"> select * from user where id = #{id} </select> </mapper>
User實體
public class User { private int id; private String username; private String password; getter and setter ..... }
Main方法
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("user.selectUser", 2); System.out.println(user.toString()); }
快速進入Debug跟蹤
咱們能夠在此處打上斷點,Debug模式啓動進入斷點,再按F7跟蹤入其方法mysql
源碼分析準備
在進行Mybatis的初始化過程以前,咱們須要把整個大綱拎出來放在前面,讓你們可以有所瞭解,而後在進行每一個步驟的時候內心有個大概;sql
- 什麼是Mybatis的初始化過程?
從代碼上來看 "SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);" 這行代碼就是執行的是Mybatis的初始化操做,這個操做一般在應用中只會操做一次,構建完成SqlSessionFactory就再也不使用,而SqlSessionFactory會跟隨整個應用生命週期;mybatis
從應用階段上來講 : Mybatis根據全局XML配置文件生成SqlSessionFactory的過程就是Mybatis的初始化過程.架構
- 淺析一詞含義
既然標題爲淺析某某....相比你們也能看出說明本章不會深度挖掘底層代碼,我我的認爲淺析一次的主要意義是 ""可以快速地在咱們心中創建底層源碼的架構圖,快速瀏覽代碼,與概念進行覈對 "",固然也不包含某些大牛謙虛的說法哈~~ 在這裏提的主要目的是,本次淺析Mybatis是快速瀏覽代碼; 稍後會出新的篇章對核心方法進行剖析app
- Mybatis初始化過程當中的主要步驟
- 將全局配置文件XML解析到Configuration對象
- 將映射配置文件XML解析到Configuration的mapperRegistry對象
- 將映射配置文件XML中的聲明(Statement)解析成MappedStatement對象存入Configuration對象的mappedStatements集合中
- 最後將Configuration最爲參數構建DefaultSqlSessionFactory對象
源碼分析
第一步: 將全局配置文件XML加載到Configuration對象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); parser.parse();
主要功能 : 將全局配置文件中的配置加載到一個Configuration對象的屬性中dom
這是第一步,咱們從Main
方法的new SqlSessionFactoryBuilder().build(inputStream)
進入斷點,能夠看到在構建完畢SqlSessionFactoryBuilder
對象後由調用了重載的build
方法ide
//SqlSessionFactoryBuilder的構造方法 public SqlSessionFactoryBuilder() { } //build方法 public SqlSessionFactory build(InputStream inputStream) { return this.build((InputStream)inputStream, (String)null, (Properties)null); } //build方法(重載) public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; try { //第一步: 建立XML配置構建器,用來解析全局XML文件內容 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException var13) { } } return var5; }
在繼續深刻以前咱們須要瞭解一下XMLConfigBuilder
這個對象,從名字上來看就能夠知道是解析XML配置文件的;XMLConfigBuilder
又繼承了BaseBuilder
類,而在BaseBuilder
類中有一個屬性Configuration
,這個Configuration
對象就是用來存儲全局配置文件和其餘Mapper的配置信息, 同時咱們從下圖也能夠看到XMLMapperBuilder
,XMLStatementBuilder
,MapperBuilderAssistant
也繼承了BaseBuilder
類源碼分析
XMLxxxBuilder是用來解析XML配置文件的,不一樣類型XMLxxxBuilder用來解析MyBatis配置文件的不一樣部位。
- XMLConfigBuilder用來解析MyBatis的全局配置文件
- XMLMapperBuilder用來解析MyBatis中的映射文件
- XMLStatementBuilder用來解析映射文件中的statement語句。
- MapperBuilderAssistant用來輔助解析映射文件並生成MappedStatement對象
這些XMLxxxBuilder都有一個共同的父類——BaseBuilder
。這個父類維護了一個全局的Configuration
對象,MyBatis的配置文件解析後就以Configuration對象的形式存儲
看源碼果真能發現貓膩,不錯不錯,能夠看到在new
這個XMLConfigBuilder
對象的時候,下圖的斷點位置super(new Configuration());
能夠看到Configuration
的構造方法以下所示,這也正解釋了咱們咱們能夠在全局配置文件中寫個JDBC就行,由於在Configuration
對象在構建的時候就加載了一些默認的別名. 別告訴我你不知作別名是啥哈~~
public Configuration() { this.safeResultHandlerEnabled = true; this.multipleResultSetsEnabled = true; this.useColumnLabel = true; this.cacheEnabled = true; this.useActualParamName = true; this.localCacheScope = LocalCacheScope.SESSION; this.jdbcTypeForNull = JdbcType.OTHER; this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString")); this.defaultExecutorType = ExecutorType.SIMPLE; this.autoMappingBehavior = AutoMappingBehavior.PARTIAL; this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; this.variables = new Properties(); this.reflectorFactory = new DefaultReflectorFactory(); this.objectFactory = new DefaultObjectFactory(); this.objectWrapperFactory = new DefaultObjectWrapperFactory(); this.lazyLoadingEnabled = false; this.proxyFactory = new JavassistProxyFactory(); this.mapperRegistry = new MapperRegistry(this); this.interceptorChain = new InterceptorChain(); this.typeHandlerRegistry = new TypeHandlerRegistry(); this.typeAliasRegistry = new TypeAliasRegistry(); this.languageRegistry = new LanguageDriverRegistry(); this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection"); this.caches = new Configuration.StrictMap("Caches collection"); this.resultMaps = new Configuration.StrictMap("Result Maps collection"); this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection"); this.keyGenerators = new Configuration.StrictMap("Key Generators collection"); this.loadedResources = new HashSet(); this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers"); this.incompleteStatements = new LinkedList(); this.incompleteCacheRefs = new LinkedList(); this.incompleteResultMaps = new LinkedList(); this.incompleteMethods = new LinkedList(); this.cacheRefMap = new HashMap(); this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class); this.typeAliasRegistry.registerAlias("LRU", LruCache.class); this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class); this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class); this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); this.languageRegistry.register(RawLanguageDriver.class); }
第一步尚未執行完? 是的上述中咱們在看構建XMLConfigBuilder
對象過程,如今構建完成了咱們就須要看這一行代碼了parser.parse();
; 當有了XMLConfigBuilder
對象以後,接下來就能夠用它來解析配置文件了
public Configuration parse() { //判斷是否已經解析,只能解析一次全局配置文件 if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { //將parsed標記爲已經解析 this.parsed = true; //解析全局配置文件的XML中的configuration節點 this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
//主要看一下解析全局配置文件的configuration節點的方法 private void parseConfiguration(XNode root) { try { //解析全局配置文件中的properties節點的配置信息存儲到Configuration對象的variables屬性中 this.propertiesElement(root.evalNode("properties")); //解析全局配置文件中的settings節點的配置信息設置到Configuration對象的各個屬性中 Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.settingsElement(settings); //解析全局配置文件中的typeAliases節點的配置信息設置到BaseBuilder對象的TypeAliasRegistry屬性中 this.typeAliasesElement(root.evalNode("typeAliases")); //解析全局配置文件的plugins this.pluginElement(root.evalNode("plugins")); //解析全局配置文件中的objectFactory設置到Configuration對象的objectFactory屬性中 this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); //解析全局配置文件中的Environment節點存儲到Configuration對象中的Environment屬性中 this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); //第二步 : 解析全局配置文件中的mappers節點 注意這是一個核心的方法 咱們點進去看一下 this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
從上述代碼中能夠看到,XMLConfigBuilder會依次解析配置文件中的<properties>、<settings>、<environments>、<typeAliases>、<plugins>、<mappers>等屬性。
第二步 : 解析映射配置文件XML到Configuration的mapperRegistry容器
this.mapperElement(root.evalNode("mappers"));
主要功能 : MyBatis會遍歷<mappers>下全部的子節點,若是當前遍歷到的節點是<package>,則MyBatis會將該包下的全部Mapper Class註冊到configuration的mapperRegistry容器中。若是當前節點爲<mapper>,則會依次獲取resource、url、class屬性,解析映射文件,並將映射文件對應的Mapper Class註冊到configuration的mapperRegistry容器中。
XMLConfigBuilder
解析全局配置文件中有一個比較重要的一步;就是解析映射文件this.mapperElement(root.evalNode("mappers"))
這句代碼開始解析映射文件,咱們開看一下下圖中構建了一個XMLMapperBuilder
對象,這個對象是負責解析映射文件的;而第一步的XMLConfigBuilder
對象是解析全局配置文件的
上圖中紅色圈中的是Mybatis解析映射文件的方法,咱們進去看一下 mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { //首先會初始化父類BaseBuilder,並將configuration賦給BaseBuilder; super(configuration); //而後建立MapperBuilderAssistant對象,該對象爲XMLMapperBuilder的協助者,用來協助XMLMapperBuilder完成一些解析映射文件的動做 this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; } public void parse() { if (!this.configuration.isResourceLoaded(this.resource)) { this.configurationElement(this.parser.evalNode("/mapper")); this.configuration.addLoadedResource(this.resource); this.bindMapperForNamespace(); } this.parsePendingResultMaps(); this.parsePendingCacheRefs(); this.parsePendingStatements(); }
再看一下mapperParser.parse();
public void parse() { //若是映射文件沒有被加載過 if (!this.configuration.isResourceLoaded(this.resource)) { //執行加載映射文件XML方法configurationElement this.configurationElement(this.parser.evalNode("/mapper")); //將此映射文件添加已經解析了的集合中 this.configuration.addLoadedResource(this.resource); //綁定Namespace this.bindMapperForNamespace(); } this.parsePendingResultMaps(); this.parsePendingCacheRefs(); this.parsePendingStatements(); }
下面是具體Mybatis解析映射文件中的Statement的過程
private void configurationElement(XNode context) { try { //獲取namespace String namespace = context.getStringAttribute("namespace"); //判斷namespace,若是爲空直接拋出異常 if (namespace != null && !namespace.equals("")) { //設置namespace this.builderAssistant.setCurrentNamespace(namespace); //下面就是解析各個Statement中的各個XML節點 this.cacheRefElement(context.evalNode("cache-ref")); this.cacheElement(context.evalNode("cache")); this.parameterMapElement(context.evalNodes("/mapper/parameterMap")); this.resultMapElements(context.evalNodes("/mapper/resultMap")); this.sqlElement(context.evalNodes("/mapper/sql")); //第三步 : 解析Statement聲明 核心方法 this.buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } else { throw new BuilderException("Mapper's namespace cannot be empty"); } } catch (Exception var3) { throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3); } }
從上述代碼中能夠看到,XMLMapperBuilder
藉助MapperBuilderAssistant
會對Mapper映射文件進行解析,在解析到最後,會將每個<mapper></mapper>中的節點解析爲MappedStatement
對象
第三步 : 解析映射文件的Statement爲MappedStatement對象
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
主要功能 : 將映射文件<mapper></mapper>的子節點解析爲MappedStatement
對象
咱們進入 this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
這個方法看一下
private void buildStatementFromContext(List<XNode> list) { if (this.configuration.getDatabaseId() != null) { this.buildStatementFromContext(list, this.configuration.getDatabaseId()); } this.buildStatementFromContext(list, (String)null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { Iterator var3 = list.iterator(); while(var3.hasNext()) { XNode context = (XNode)var3.next(); XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException var7) { this.configuration.addIncompleteStatement(statementParser); } } }
其中主要的邏輯都在下示圖中的兩行代碼中
接下來咱們進入XMLStatementBuilder類的parseStatementNode
去看看
最終由MapperBuilderAssistant
完成MappedStatement
對象的封裝,而且將MappedStatement
對象放入Configuration
對象的**mappedStatements
**容器中
初始化完成
主要功能 : 將已經裝載了各類XML信息的Configuration對象做爲參數構建DefaultSqlSessionFactory返回,Mybatis初始化完成!!!
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
該教程所屬Java工程師之Spring Framework深度剖析專欄,本系列相關博文目錄 Java工程師之Spring Framework深度剖析專欄