開心一刻html
本人幼教老師,冬天戴帽子進教室,被小朋友看到,這時候,有個小傢伙對我說:老師你的帽子太醜,趕忙摘了吧。我逗他:那你好好學習,之後給老師買個漂亮的?這孩子想都沒想馬上回答:等我賺錢了,帶你去韓國整形git
咱們先來看一個純粹的mybatis示例(不集成spring等其餘框架),代碼很簡單,結構以下spring
完整代碼地址:mybatis;mapper層和咱們平時說的dao層指的是同一個內容,都是數據庫操做的封裝,可是在沒有集成mybatis時,dao層的接口都是須要咱們手動去寫其實現類,可在上圖中咱們卻發現:咱們並無手動去實現PersonMapper接口,但工程卻能實實在在的查詢數據庫,獲取咱們須要的數據,以下圖所示sql
從上圖咱們發現,PersonMapper實例是一個代理對象,咱們操做的實際上是PersonMapper的代理實現;也就是說不用咱們手動去實現PersonMapper接口,mybatis會動態生成PersonMapper的代理實例,而後由代理實例完成數據庫的操做數據庫
那麼問題來了,mybatis是什麼時候、何地、如何生成mapper代理實例的呢?咱們接着往下看設計模式
針對上述問題,咱們來跟下mybatis源碼springboot
XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),將配置文件中各個屬性解析到Configuration實例中,而後以Configuration實例構建SqlSessionFactory(實際是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具體過程,有興趣的能夠更深一步的去探究mybatis
/** * root是以configuration標籤開始的文檔樹 * 解析配置文件中的各個標籤,並存放到Configuration實例對應的屬性中 * 解析完成以後,配置文件中的內容所有解析到了Configuration實例中 * @param root */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); // 解析配置文件中的properties標籤 Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析配置文件中的settings標籤 loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // 解析配置文件中的typeAliases標籤 pluginElement(root.evalNode("plugins")); // 解析配置文件中的plugins標籤 objectFactoryElement(root.evalNode("objectFactory")); // 解析配置文件中的objectFactory標籤 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析配置文件中的objectWrapperFactory標籤 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 解析配置文件中的reflectorFactory標籤 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); // 解析配置文件中的environments標籤 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析配置文件中的databaseIdProvider標籤 typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置文件中的typeHandlers標籤 mapperElement(root.evalNode("mappers")); // 解析配置文件中的mappers標籤 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
上述代碼中的mapperElement(root.evalNode("mappers"));是否是很誘人?與咱們的mapper有關係,是否是在這裏就生成了mapper的代理實例,仍是隻是讀取了mapper配置文件的內容?暫時還不敢確定,那麼咱們跟進去看看app
其中有兩個方法值得重點關注下,具體以下,裏面的註釋能夠重點看下,有興趣的能夠更進一步的跟進去框架
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); // 解析映射文件Person.xml configuration.addLoadedResource(resource); bindMapperForNamespace(); // 將mapper與namespace綁定起來; 將PersonMapper接口與MapperProxyFactory關聯起來 } parsePendingResultMaps(); // 解析Configuration的incompleteResultMaps到Configuration的resultMaps parsePendingCacheRefs(); // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap parsePendingStatements(); // 解析Configuration的incompleteStatements到Configuration的mappedStatements } /** * context是映射文件:Person.xml的文檔樹,以mapper標籤開始 * 解析映射文件中的各個標籤,並存放到MapperBuilderAssistant實例對應的屬性中 */ private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); // 解析mapper標籤的namespace屬性 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // namespace屬性值解析到Configuration的mapperRegistry中 cacheRefElement(context.evalNode("cache-ref")); // 解析cache-ref標籤到Configuration的cacheRefMap中 cacheElement(context.evalNode("cache")); // 解析cache標籤到Configuration的caches中 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析parameterMap標籤到Configuration的parameterMaps中 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析resultMap標籤到Configuration的resultMaps中 sqlElement(context.evalNodes("/mapper/sql")); // 解析sql標籤到XMLMapperBuilder的sqlFragments中 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析select|insert|update|delete標籤到Configuration的mappedStatements中 } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
此時SqlSessionFactory已經建立,但PersonMapper的代理實例尚未建立;期間準備了不少東西,包括讀取配置文件和映射文件的內容,並將其放置到Configuration實例的對應屬性中
實例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此時mapper代理實例仍未被建立
能夠看到,最終仍是利用了JDK的動態代理
protected T newInstance(MapperProxy<T> mapperProxy) { // 利用JDK的動態代理生成mapper的代理實例 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
生成了mapper的代理實例,後續就能夠利用此代理實例進行數據庫的操做了
更多動態代理的信息請查看:設計模式之代理,手動實現動態代理,揭祕原理實現
一、咱們用mytabis操做數據庫,有一個固定流程:先建立SqlSessionFactory,而後建立SqlSession,而後再建立獲取mapper代理對象,最後利用mapper代理對象完成數據庫的操做;一次數據庫操做完成後須要關閉SqlSession;
二、建立SqlSessionFactory實例的過程當中,解析mybatis配置文件和映射文件,將內容都存放到Configuration實例的對應屬性中;建立SqlSession的過程當中,有建立事務Transaction、執行器Executor,以及DefaultSqlSession;Mapper代理對象的建立,利用的是JDK的動態代理,InvocationHandler是MapperProxy,後續Mapper代理對象方法的執行都會先通過MapperProxy的invoke方法;
三、不少細節沒有講到,但大致流程就是這樣;另外提下,實際應用中,mybatis每每不會單獨使用,絕大多數都是集成在spring中;關於在spring的集成下,mapper代理對象的建立過程查看:springboot集成下,mybatis的mapper代理對象到底是如何生成的