前言
如何調試源碼請參考這一篇文章《如何調試Mybatis源碼 》。
注意:如果你還沒有調試過mybatis,那麼不建議你往下閱讀,因為你會看的稀裡糊塗-。-
。
本篇我們就一塊Debug源碼,看看Mybatis到底是如何讀取到 mybatis-config.xml 檔案中的一系列參數的呢?
首先我們看張圖:這是我們JDBC時對資料庫的一系列操作,先擷取資料源中一系列的參數,然後執行SQL語句,最後對資料庫進行操作,擷取到結果。
本章節就先說說第一部分,mybatis如何擷取到資料源。
Mybatis如何擷取資料源
在 MyBatis 初始化過程中,會加載 mybatis-config.xml 配置檔案、映射配置檔案以及 Mapper 接口中的注解資訊,解析後的配置資訊會形成相應的對象并儲存到 Configuration 對象中。利用該 Configuration 對象建立 SqlSessionFactory對象。待 MyBatis 初始化之後,開發人員可以通過初始化得到 SqlSessionFactory 建立 SqlSession 對象并完成資料庫操作。
- 對應
子產品,為配置解析過程。主要的xml檔案的解析都在這個子產品下進行。 第一步要做的事情就是根據配置檔案建構SqlSessionFactory對象。,使用builder
工具類加載了Resources
檔案,得到輸入流,然後通過mybatis-config.xml
的SqlSessionFactoryBuilder
方法,建構出build()
對象。sqlSessionFactory
public static void main(String[] args) throws IOException {
// 擷取配置檔案
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 建構SqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
User user = sqlSession.selectOne("com.mybatis.read.UserDao.selectUser", 1);
} finally {
sqlSession.close();
}
}
SqlSessionFactoryBuilder
可以看出這裡調用了
XMLConfigBuilder
對象的
parse()
方法,對 xml 檔案進行了解析,那麼如何解析的呢?進入
XMLConfigBuilder
類中一探究竟吧。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 建立 XMLConfigBuilder 對象
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
XMLConfigBuilder
- Configuration 這個對象貫穿了整個配置檔案初始化的過程。
- 對xml檔案中的标簽做了解析,從源碼中可以看出幾個關鍵的标簽有
,configuration
,properties
,environments
,dataSource
。mapper
- 通過
、configuration.setVariables()
将配置檔案中的configuration.setEnvironment()
和properties
下的參數放到了environments
對象中供全局使用Configuration
public Configuration parse() {
// 隻加載一次xml檔案,否則抛出異常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析xml檔案中configuration标簽
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 功能描述: 解析 configuration 标簽下的所有屬性配置
* @date 2020/2/27
*/
private void parseConfiguration(XNode root) {
try {
// 讀取mybatis-config.xml中的properties,加載config.properties檔案中的參數
propertiesElement(root.evalNode("properties"));
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"));
settingsElement(settings);
// 加載environments節點
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);
}
}
解析 properties 配置
properties 的解析過程比較簡單,由代碼中可以看出,屬性配置是從
resource
或者
url
中擷取的,即檔案系統或者網絡位址。
項目中配置的是
resource
,
<properties resource = "config.properties"/>
。将
config.properties
檔案中的内容讀取出來,然後設定到
XPathParser
和
Configuration
對象中。
config.properties檔案内容如下:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=root
/***
* 功能描述:解析properties節點,擷取url,driver,username,password
* @date 2020/2/27
*/
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// properties 标簽裡的屬性,隻能設定resource或者url其中一個,否則抛出異常
if (resource != null && url != null) {
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的檔案,把裡面的參數放到defaults中
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
defaults
中的參數如下:
解析 environments 配置
在 MyBatis 中,事務管理器和資料源是配置在 environments 中的。它們的配置大緻如下:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
解析
environments
标簽,之後解析了内部的
dataSource
标簽。
/***
* 功能描述:解析 environments 标簽
* @date 2020/2/27
*/
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"));
// 解析environment下的dataSource标簽
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
/**
* 功能描述:解析 dataSource 标簽
* @date 2020/2/27
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
dataSource
中的參數如下:
以上代碼的流程簡單的花了一個圖,可以大緻明白是怎樣的一個流程:
小結
至此,我們已經可以看出Mybatis是如何從配置檔案中加載出資料源了。
在解析
Configuration
标簽時我們隻關注了和資料源參數相關的
properties
,
environments
标簽,還有一些同樣重要的标簽沒有展開說明,有興趣的同學同樣可以自行debug到代碼中,看看其他參數是設定,并産生作用的。
下一節說一說 Mapper 映射配置檔案 是如何加載的。