開篇
過去一周忙着上線一個線上服務,沒時間閱讀源碼,幸好服務已經順利上線,可以抽空開始mybatis的系列了,沒錯這周開始準備開啟mybatis的整個系列,歡迎大家訂閱。
按照我現在粗淺的了解,從mybatis的使用過程來看基本可以分為三大步驟,分别是:
- 配置加載 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
- 會話建立 SqlSession sqlSession = sqlSessionFactory.openSession();
- 執行查詢 User user = sqlSession.selectOne("userTest.selectUser", 1);
這篇博文主要講解下配置加載的過程,由于配置加載涉及很多标簽的加載,這篇文章暫時隻分析宏觀的過程,細節每個标簽的加載後面文章再分析,核心需要記住mybatis的核心是 all in configuration
補充一點大家不要把mybatis和spring使用mybatis直接混在一起,因為spring和mybatis之間隔了一層spring-mybatis的實作,我們這邊隻講解最純的mybatis。
解析過程概述
在mybatis的使用過程中,我們建構一個配置的輸入reader并通過SqlSessionFactoryBuilder解析配置生成SqlSessionFactory,我們的解析過程就在建立SqlSessionFactory的過程當中。
public class MyBatisTest {
public static void main(String[] args) throws Exception {
String resource = "configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("userTest.selectUser", 1);
System.out.println(user.getUsername());
sqlSession.close();
}
}
配置解析過程
環境配置xml檔案
mybatis的xml配置當中我認為可以分為兩大塊,分别是environment和mappers,前者主要是指整個mybatis連接配接的環境配置,後者表示mybatis寫的SQL語句,配置的解析過程其實就是解析這塊内容的。
關于mybatis支援的xml标簽可以參考文章中的mybatis官網,相信我看了以後你一定不會後悔的,包括:
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- plugins 插件
- environments 環境
- databaseIdProvider 資料庫廠商辨別
- mappers 映射器
<?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/mybatis?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 映射檔案 -->
<mappers>
<mapper resource="map/query.xml" />
<mapper resource="map/insert.xml" />
<mapper resource="map/update.xml" />
<mapper resource="map/delete.xml" />
</mappers>
</configuration>
環境配置解析源碼分析
在XMLConfigBuilder類當中parseConfiguration負責解析上面的xml檔案,基本上每個标簽對應的都有一個解析類,這裡我們就不展開了,每個标簽的解析其實都是一個比較複雜的邏輯。
我們關注下mapperElement方法,這個方法是解析mappers标簽引入的對應的SQL語句的xml檔案,通過Resources.getResourceAsStream方法讀取xml檔案内容,通過mapperParser.parse()方法進行解析。
private void parseConfiguration(XNode root) {
try {
//分步驟解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.類型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.對象工廠
objectFactoryElement(root.evalNode("objectFactory"));
//5.對象包裝工廠
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.設定
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.環境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.類型處理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
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) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,
resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
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<?> 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.");
}
}
}
}
}
SQL解析
SQL的XML格式
我們通過标簽<mapper resource="map/insert.xml" />引入的SQL檔案的内容如下,mapper其實包含的标簽個數不是特别多,可以給大家感受下:
- cache – 給定命名空間的緩存配置。
- cache-ref – 其他命名空間緩存配置的引用。
- resultMap – 是最複雜也是最強大的元素,用來描述如何從資料庫結果集中來加載對象。
- parameterMap – 已廢棄!老式風格的參數映射。内聯參數是首選,這個元素可能在将來被移除,這裡不會記錄。
- sql – 可被其他語句引用的可重用語句塊。
- insert – 映射插入語句
- update – 映射更新語句
- delete – 映射删除語句
- select – 映射查詢語句
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.daoInterface.UserDao">
<resultMap id="UserMap" type="domain.UserEntity">
<id property="id" column="uid" />
<result property="username" column="username" />
<result property="password" column="password"/>
<result property="address" column="address"/>
<result property="createTime" column="createTime"/>
<result property="updateTime" column="updateTime"/>
<collection property="campanyEntity" resultMap="dao.daoInterface.CampanyDao.CampanyMap" />
</resultMap>
<!-- 可以将sql語句獨立出來,然後引用 -->
<sql id="selectMap">
u.username, u.address ,c.campany_name
</sql>
<!-- 根據id查詢使用者 -->
<select id="getUserInfo" parameterType="int" resultMap="UserMap">
SELECT <include refid="selectMap"/>
FROM user u left join campany c
ON u.username = c.username
WHERE id = #{id}
ORDER BY id ASC
</select>
</mapper>
SQL解析源碼分析
在XMLMapperBuilder類中通過parse進行解析,從源碼可以看出來從标簽/mapper開始解析,一次解析SQL對應的各類标簽,針對每個标簽的解析都是比較複雜的,這個暫時不展開進行分析。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
}
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"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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);
}
}
配置解析過程流程圖
核心在于XMLConfiggBuilder解析環境變量和XMLMapperBuilder解析SQL變量,這個圖應該還算比較清晰,通過解析後核心就生成了Configuration對象,這個是整個mybatis的核心
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuMWNlRmZ3kDM3EWYkVjNiZDZ5UzNxETM1MTMlNWMmRDZfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
配置解析時序圖