樓主比較菜,肯定有很多說的不對的地方,主要還是寫給自己看的!!
比起spring來說,mybatis實在是簡單,所以就先來聊聊mybatis!
先來張mybatis整體的結構圖 瞧瞧
從MyBatis代碼實現的角度來看,MyBatis的主要的核心部件有以下幾個:
1 SqlSession 作爲MyBatis工作的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪改查功能
2 Executor MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護
3 StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。
4 ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所需要的參數,
5 ResultSetHandler 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合;
6 TypeHandler 負責java數據類型和jdbc數據類型之間的映射和轉換
7 MappedStatement MappedStatement維護了一條select|update|delete|insert節點的封裝
8 SqlSource 負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
9 BoundSql 表示動態生成的SQL語句以及相應的參數信息
10 Configuration MyBatis所有的配置信息都維持在Configuration對象之中。
我們用一個列子來看下mybatis 的配置模塊
String resource = "configs/mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
SqlSession session = sqlSessionFactory.openSession();
1 根據 配置文件或者註解,生成和數據庫交互的必要的數據,存儲於Map中,以供後續使用;
1.1 我們來看下 Reader reader = Resources.getResourceAsReader(resource);
resource 地址我是直接用的resrouces文件夾中的相對地址,作用就是根據地址加載配置文件信息,輸出Reader;
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
類加載器爲上述5種,可以看出,以傳入的classLoader和默認的defaultClassLoader爲主,下面三個大家都很熟悉了,那麼一般我們用第三個;這裏用到的兩個爲 org.apache.ibatis.io.Resources(主要用來解析文件,還可以返回Class)和org.apache.ibatis.io.ClassLoaderWrapper(主要是涉及到類加載器的用途,無非是加載文件和加載類),
這裏主要涉及到org.apache.ibatis.io包,看包名就知道 ,該包下面都是關於io的類
包中就這麼幾個類,其中vfs類(虛擬文件系統,用來讀取服務器裏的資源),提供了2個實現 JBoss6VFS 和 DefaultVFS,並提供了用戶擴展點,可定義VFS實現;加載順序: 自定義VFS實現 > 默認VFS實現 取第一個加載成功的
1.2 根據Reader解析成mybatis必須數據
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
1.2.1 好了,來看下SqlSessionFactoryBuilder 這個類,主要集中在build方法中,該方法提供了很多的重載方法,不一一說,重點說下下面的方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
看出,不僅僅可以傳reader,還可以自定義environment(jdbc連接條件)和properties(變量)這兩個配置信息,可以看出 重點又在XMLConfigBuilder這個類中了
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
XPathParser 解析類,主要封裝 通過javax.xml.xpath.XPath來生成Document,具體怎麼解析就不說了,不是本文重點;
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
出現Configuration 這個類,上面講到過 , MyBatis所有的配置信息都維持在Configuration對象之中。從這裏才真正的開始解析配置文件至mybatis中;Configuration 默認構造函數中就註冊了很多別名,其實就是放進map中,這種方式在很多地方都會見到;
1.2.2 parser.parse()
來看看 ,parse方法中發生了什麼;
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
重點是parseConfiguration這個方法,可以看出,先從configuration開始解析了;
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
很明顯,所有的配置信息全部在改方法中生成,我們挑些講講;
1.2.2.1 propertiesElement(root.evalNode(“properties”));
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
由上可知,屬性變量有三種方式可以加載進來,節點子集、resource和url節點屬性,其中resource和url只能存在一個,最終合併放進配置文件中
parser.setVariables(defaults);
configuration.setVariables(defaults);
1.2.2.2
typeAliasesElement(root.evalNode("typeAliases"));
註冊別名,簡單點說就是以別名爲key,class爲value存放於對應的map中,以便後續用
1.2.2.3 pluginElement(root.evalNode("plugins"));
註冊插件;先註冊進別名,然後放入插件鏈中
1.2.2.4 objectFactoryElement(root.evalNode("objectFactory"));
類創建工程,該類作用僅僅是生成實例,默認是DefaultObjectFactory;我們可以實現我們自定義的工廠,實現ObjectFactory接口即可,可以用於類初始化的作用
1.2.2.5 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
動態獲取和設置屬性的值,默認使用DefaultObjectWrapperFactory,mybatis基本考慮會很全,自定義的很少使用
對象包裝工程
1.2.2.6 reflectorFactoryElement(root.evalNode("reflectorFactory"));
反射工廠,功能很簡單,就是生成一個反射配置數據,存儲Reflector類(裏面包含了該類涉及到方法、構造函數、字段、類型很全的一套反射信息,賦值、取值都可以通過他來操作。我們以後自己項目也可以直接拿來使用)數據,默認使用DefaultReflectorFactory類
1.2.2.7 environmentsElement
主要配置連接執行環境,裏面包含了事務(JdbcTransactionFactory還有ManagedTransactionFactory,一般使用前者)及數據源(POOL、UNPOOL,JNDI之分,一般肯定選擇池)的配置信息的生成;最終生成Environment;Environment這個類比較奇怪,裏面實現了內部類Builder,但是內部類和外部類區別不大 ,何必呢。。
1.2.2.8 typeHandlerElement(root.evalNode("typeHandlers"));
類型轉換器,該配置可以根據包名,進而解析整個包獲取,也可以指定轉換類,因爲typeHandlerRegistry類中可以對包進行註冊
1.2.2.9 mapperElement(root.evalNode("mappers"));
xml配置文件解析,同.1.2.2.8 可以對包(只能在mapper類同包名下才行),也可以其他方式
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
由上可以看出是三種方式,其中數resource和url複雜,class是很簡單的,
我們這裏舉resource的例子,這裏涉及到XMLMapperBuilder類
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
好嘛,mapper.xml解析入口在這裏了。好複雜。。
XMLMapperBuilder(解析mapper.xml配置信息類)和XmlConfigBuilder類似,都繼承BaseBuilder,同樣都有parse 解析方法,只是XMLMapperBuilder更復雜些,因爲mapper.xml中的節點更多更復雜。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
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. Cause: " + e, e);
}
}
從configurationElement方法可以看出,xml直接關聯唯一mapper類,那麼可以以此作爲key,進而爲後續調用獲取配置信息打下基礎。
MapperBuilderAssistant用於緩存、sql參數、查詢返回的結果集處理。
SQL 映射文件有很少的幾個頂級元素(按照它們應該被定義的順序):
cache – 給定命名空間的緩存配置。可以配置
映射語句文件中的所有 select 語句將會被緩存。
映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。
緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
根據時間表(比如 no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序 來刷新。
緩存會存儲列表集合或對象(無論查詢方法返回什麼)的 1024 個引用。
緩存會被視爲是 read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而 且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
cache-ref – 其他命名空間緩存配置的引用。共用一個namespace緩存
resultMap – 是最複雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
這裏解析的resultMap中的數據,其中涉及到ResultMapping類,主要記錄對應的表及實體類相關配置數據,存進resultMappings 中
parameterMap – 已廢棄!老式風格的參數映射。內聯參數是首選,這個元素可能在將來被移除,這裏不會記錄。
sql – 可被其他語句引用的可重用語句塊。
這塊僅僅是將配置信息存入,而沒有進一步去解析
insert – 映射插入語句
update – 映射更新語句
delete – 映射刪除語句
select – 映射查詢語句
上面四種都是一樣處理,僅僅是類型不一樣而已;
其中涉及到XMLStatementBuilder類,該類主要記錄Statement相關的配置信息MappedStatement
好了 最麻煩的也處理完成了!!返回Configuration,所有的配置信息全在裏面了
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
返回DefaultSqlSessionFactory
接下來是下面這段代碼
SqlSession session = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
開啓會話,來看看到底幹什麼了!
看代碼,初始化了事務Transaction,根據例子,其實這裏真實的對象應該是JdbcTransaction
也初始化了Executor ,mybatis的執行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
看代碼,executor 最終被CachingExecutor裝飾了,執行時,先執行CachingExecutor,再執行SimpleExecutor(我們例子裏是simple)
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
額,構造函數裏又把CachingExecutor傳遞給SimpleExecutor了,這是要幹啥,沒看到什麼用途,先不管,繼續繼續!
executor = (Executor) interceptorChain.pluginAll(executor);
將執行器放入插件鏈中,判斷是否符合自定義插件類型,符合 則生成代理,則以後凡是到了執行器這裏,則優先進入自定義插件執行!
這就是製作插件的原理,使用代理!
最終返回DefaultSqlSession,又是個Default
好了,會話成功開啓!
總結:
mybatis配置階段,使用了共享模式、裝飾模式、代理模式、工廠模式、模板模式、外觀模式。
插件的原理是使用代理(jdk代理(必須要有接口)或者cglib代理(類),兩者性能都很高),用jdk的動態代理實現了攔截器和Mapper接口。這種動態代理和註解的運用也是非常值得學習的。
好吧!寫的很爛,連圖都沒畫!
下一篇準備mybatis執行部分解析