Mybatis對比JDBC的優勢:
(1)使用連接配接池管理資料庫連接配接資料庫連接配接,避免了JDBC頻繁的連接配接和斷開資料庫資源;
(2)使用mapper.xml配置檔案的形式,将sql語句與java代碼分離;
(3)mybatis通過parameterType向sql中傳遞參數,更加靈活;
(4)mybatis可以将查詢結果自動映射為java對象,避免了jdbc手工寫代碼的繁瑣過程。
Mybatis源碼解析過程——解析mybtatis-config.xml全局配置檔案
解析mybatis-config.xml配置檔案
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
解析properties節點
解析setting節點
解析我們的mybatis環境,也就是資料庫連接配接配置;
解析到:org.apache.ibatis.session.Configuration#environment
* 在內建spring情況下由 spring-mybatis提供資料源 和事務工廠
*
解析我們的類型處理器節點
解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap
解析解析我們的mapper
Mybatis源碼解析過程——解析mapper.xml配置檔案
(1)mybatis中mapper的四種配置方式
(1)package:批量解析mapper配置檔案
(2)resource:按照配置解析mapper檔案
(3)url:從網絡資源或者本地磁盤解析mapper檔案
(4)class:要求接口和xml在同一個包下面
/**
* 判斷我們mapper是不是通過批量注冊的
* <package name="com.tuling.mapper"></package>
*/
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
/**
* 判斷從classpath下讀取我們的mapper
* <mapper resource="mybatis/mapper/EmployeeMapper.xml"/>
*/
String resource = child.getStringAttribute("resource");
/**
* 判斷是不是從我們的網絡資源讀取(或者本地磁盤得)
* <mapper url="D:/mapper/EmployeeMapper.xml"/>
*/
String url = child.getStringAttribute("url");
/**
* 解析這種類型(要求接口和xml在同一個包下)
* <mapper class="com.tuling.mapper.DeptMapper"></mapper>
*
*/
String mapperClass = child.getStringAttribute("class");
(2)解析package配置的mapper.xml
org.apache.ibatis.binding.MapperRegistry#addMapper
// 根據mapper接口名擷取 xml檔案并解析, 解析<mapper></mapper>裡面所有東西放到configuration
loadXmlResource();
// 添加已解析的标記
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
根據mapper接口的名稱,轉換處理為xml檔案的實體路徑,然後利用XMLMapperBuilder類進行解析。
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
try {
/**
* 解析我們的namespace屬性
* <mapper namespace="com.tuling.mapper.EmployeeMapper">
*/
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
/**
* 儲存我們目前的namespace 并且判斷接口完全類名==namespace
*/
builderAssistant.setCurrentNamespace(namespace);
/**
* 解析我們的緩存引用
* 說明我目前的緩存引用和DeptMapper的緩存引用一緻
* <cache-ref namespace="com.tuling.mapper.DeptMapper"></cache-ref>
解析到org.apache.ibatis.session.Configuration#cacheRefMap<目前namespace,ref-namespace>
異常下(引用緩存未使用緩存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 解析我們的cache節點
* <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
解析到:org.apache.ibatis.session.Configuration#caches
org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
*/
cacheElement(context.evalNode("cache"));
/**
* 解析paramterMap節點(該節點mybaits3.5貌似不推薦使用了)
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 解析我們的resultMap節點
* 解析到:org.apache.ibatis.session.Configuration#resultMaps
* 異常 org.apache.ibatis.session.Configuration#incompleteResultMaps
*
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 解析我們通過sql節點
* 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
* 其實等于 org.apache.ibatis.session.Configuration#sqlFragments
* 因為他們是同一引用,在建構XMLMapperBuilder 時把Configuration.getSqlFragments傳進去了
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 解析我們的select | insert |update |delete節點
* 解析到org.apache.ibatis.session.Configuration#mappedStatements
*/
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);
}
}
(3)解析mapper.xml中配置的sql語句
将sql中的内容解析成MappedStatement對象,然後存入configuration對象中。
public void parseStatementNode() {
/**
* 我們的insert|delte|update|select 語句的sqlId
*/
String id = context.getStringAttribute("id");
/**
* 判斷我們的insert|delte|update|select 節點是否配置了
* 資料庫廠商标注
*/
String databaseId = context.getStringAttribute("databaseId");
/**
* 比對目前的資料庫廠商id是否比對目前資料源的廠商id
*/
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/**
* 獲得節點名稱:select|insert|update|delete
*/
String nodeName = context.getNode().getNodeName();
/**
* 根據nodeName 獲得 SqlCommandType枚舉
*/
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
/**
* 判斷是不是select語句節點
*/
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
/**
* 擷取flushCache屬性
* 預設值為isSelect的反值:查詢:預設flushCache=false 增删改:預設flushCache=true
*/
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
/**
* 擷取useCache屬性
* 預設值為isSelect:查詢:預設useCache=true 增删改:預設useCache=false
*/
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
/**
* resultOrdered: 是否需要處理嵌套查詢結果 group by (使用極少)
* 可以将比如 30條資料的三組資料 組成一個嵌套的查詢結果
*/
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
/**
* 解析我們的sql公用片段
* <select id="qryEmployeeById" resultType="Employee" parameterType="int">
<include refid="selectInfo"></include>
employee where id=#{id}
</select>
将 <include refid="selectInfo"></include> 解析成sql語句 放在<select>Node的子節點中
*/
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
/**
* 解析我們sql節點的參數類型
*/
String parameterType = context.getStringAttribute("parameterType");
// 把參數類型字元串轉化為class
Class<?> parameterTypeClass = resolveClass(parameterType);
/**
* 檢視sql是否支撐自定義語言
* <delete id="delEmployeeById" parameterType="int" >
<settings>
<setting name="defaultScriptingLanguage" value="tulingLang"/>
</settings>
*/
String lang = context.getStringAttribute("lang");
/**
* 擷取自定義sql腳本語言驅動 預設:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
*/
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
/**
* 解析我們<insert 語句的的selectKey節點, 還記得吧,一般在oracle裡面設定自增id
*/
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
/**
* 我們insert語句 用于主鍵生成元件
*/
KeyGenerator keyGenerator;
/**
* selectById!selectKey
* id+!selectKey
*/
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
/**
* 把我們的命名空間拼接到keyStatementId中
* com.tuling.mapper.Employee.saveEmployee!selectKey
*/
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
/**
*<insert id="saveEmployee" parameterType="com.tuling.entity.Employee" useGeneratedKeys="true" keyProperty="id">
*判斷我們全局的配置類configuration中是否包含以及解析過的元件生成器對象
*/
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
/**
* 若我們配置了useGeneratedKeys 那麼就去除useGeneratedKeys的配置值,
* 否者就看我們的mybatis-config.xml配置檔案中是配置了
* <setting name="useGeneratedKeys" value="true"></setting> 預設是false
* 并且判斷sql操作類型是否為insert
* 若是的話,那麼使用的生成政策就是Jdbc3KeyGenerator.INSTANCE
* 否則就是NoKeyGenerator.INSTANCE
*/
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
/**
* 通過class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver來解析我們的
* sql腳本對象 . 解析SqlNode. 注意, 隻是解析成一個個的SqlNode, 并不會完全解析sql,因為這個時候參數都沒确定,動态sql無法解析
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
/**
* STATEMENT,PREPARED 或 CALLABLE 中的一個。這會讓 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED
*/
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
/**
* 這是一個給驅動的提示,嘗試讓驅動程式每次批量傳回的結果行數和這個設定值相等。 預設值為未設定(unset)(依賴驅動)
*/
Integer fetchSize = context.getIntAttribute("fetchSize");
/**
* 這個設定是在抛出異常之前,驅動程式等待資料庫傳回請求結果的秒數。預設值為未設定(unset)(依賴驅動)。
*/
Integer timeout = context.getIntAttribute("timeout");
/**
* 将會傳入這條語句的參數類的完全限定名或别名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler) 推斷出具體傳入語句的參數,預設值為未設定
*/
String parameterMap = context.getStringAttribute("parameterMap");
/**
* 從這條語句中傳回的期望類型的類的完全限定名或别名。 注意如果傳回的是集合,那應該設定為集合包含的類型,而不是集合本身。
* 可以使用 resultType 或 resultMap,但不能同時使用
*/
String resultType = context.getStringAttribute("resultType");
/**解析我們查詢結果集傳回的類型 */
Class<?> resultTypeClass = resolveClass(resultType);
/**
* 外部 resultMap 的命名引用。結果集的映射是 MyBatis 最強大的特性,如果你對其了解透徹,許多複雜映射的情形都能迎刃而解。
* 可以使用 resultMap 或 resultType,但不能同時使用。
*/
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
/**
* 解析 keyProperty keyColumn 僅适用于 insert 和 update
*/
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
/**
* 為我們的insert|delete|update|select節點建構成我們的mappedStatment對象
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
根據解析後的configuration對象生成sqlsessionfactory對象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}