目錄
搭建源碼環境
在這裏我提一下,在早期Mybatis版本中,Dao開發方式都是有Mapper接口和其實現類的,實現類是需要我們自己編寫的,後來Mybatis使用JDK動態代理針對Mapper接口做了代理,替我們實現了實現類; 但是其底層也是使用了Mapper接口的實現類,不可能說只有一個即可就能和JDBC進行通訊 ! 其基礎環境搭建可參照官方教程 http://www.mybatis.org/mybatis-3/zh/getting-started.html
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跟蹤入其方法
源碼分析準備
在進行Mybatis的初始化過程之前,我們需要把整個大綱拎出來放在前面,讓大家能夠有所瞭解,然後在進行每個步驟的時候心裏有個大概;
- 什麼是Mybatis的初始化過程?
從代碼上來看 "SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);" 這行代碼就是執行的是Mybatis的初始化操作,這個操作通常在應用中只會操作一次,構建完成SqlSessionFactory就不再使用,而SqlSessionFactory會跟隨整個應用生命週期;
從應用階段上來說 : Mybatis根據全局XML配置文件生成SqlSessionFactory的過程就是Mybatis的初始化過程.
- 淺析一詞含義
既然標題爲淺析某某....相比大家也能看出說明本章不會深度挖掘底層代碼,我個人認爲淺析一次的主要意義是 ""能夠快速地在我們心中建立底層源碼的架構圖,快速瀏覽代碼,與概念進行覈對 "",當然也不包含某些大牛謙虛的說法哈~~ 在這裏提的主要目的是,本次淺析Mybatis是快速瀏覽代碼; 稍後會出新的篇章對核心方法進行剖析
- 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對象的屬性中
這是第一步,我們從Main
方法的new SqlSessionFactoryBuilder().build(inputStream)
進入斷點,可以看到在構建完畢SqlSessionFactoryBuilder
對象後由調用了重載的build
方法
//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會依次解析配置文件中的
第二步 : 解析映射配置文件XML到Configuration的mapperRegistry容器
this.mapperElement(root.evalNode("mappers"));
主要功能 : MyBatis會遍歷
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映射文件進行解析,在解析到最後,會將每一個MappedStatement
對象
第三步 : 解析映射文件的Statement爲MappedStatement對象
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
主要功能 : 將映射文件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深度剖析專欄