摘要:下面我會詳細地從源碼的角度分析下文簡寫成是如何實現自動注入的原理。文件解析器,解析對應的文件信息,并將文件信息注冊到中。節點解析器,用于構建節點信息。注冊與綁定類,將的類信息與綁定。
微信公眾號「后端進階」,專注后端技術分享:Java、Golang、WEB框架、分布式中間件、服務治理等等。
老司機傾囊相授,帶你一路進階,來不及解釋了快上車!
mybatis-plus是完全基于mybatis開發的一個增強工具,它的設計理念是在mybatis的基礎上只做增強不做改變,為簡化開發、提高效率而生,它在mybatis的基礎上增加了很多實用性的功能,比如增加了樂觀鎖插件、字段自動填充功能、分頁插件、條件構造器、sql注入器等等,這些在開發過程中都是非常實用的功能,mybatis-plus可謂是站在巨人的肩膀上進行了一系列的創新,我個人極力推薦。下面我會詳細地從源碼的角度分析mybatis-plus(下文簡寫成mp)是如何實現sql自動注入的原理。
溫故知新我們回顧一下mybatis的Mapper的注冊與綁定過程,我之前也寫過一篇「Mybatis源碼分析之Mapper注冊與綁定」,在這篇文章中,我詳細地講解了Mapper綁定的最終目的是將xml或者注解上的sql信息與其對應Mapper類注冊到MappedStatement中,既然mybatis-plus的設計理念是在mybatis的基礎上只做增強不做改變,那么sql注入器必然也是在將我們預先定義好的sql和預先定義好的Mapper注冊到MappedStatement中。
現在我將Mapper的注冊與綁定過程用時序圖再梳理一遍:
解析一下這幾個類的作用:
SqlSessionFactoryBean:繼承了FactoryBean和InitializingBean,符合spring loc容器bean的基本規范,可在獲取該bean時調用getObject()方法到SqlSessionFactory。
XMLMapperBuilder:xml文件解析器,解析Mapper對應的xml文件信息,并將xml文件信息注冊到Configuration中。
XMLStatementBuilder:xml節點解析器,用于構建select/insert/update/delete節點信息。
MapperBuilderAssistant:Mapper構建助手,將Mapper節點信息封裝成statement添加到MappedStatement中。
MapperRegistry:Mapper注冊與綁定類,將Mapper的類信息與MapperProxyFactory綁定。
MapperAnnotationBuilder:Mapper注解解析構建器,這也是為什么mybatis可以直接在Mapper方法添加注解信息就可以不用在xml寫sql信息的原因,這個構建器專門用于解析Mapper方法注解信息,并將這些信息封裝成statement添加到MappedStatement中。
從時序圖可知,Configuration配置類存儲了所有Mapper注冊與綁定的信息,然后創建SqlSessionFactory時再將Configuration注入進去,最后經過SqlSessionFactory創建出來的SqlSession會話,就可以根據Configuration信息進行數據庫交互,而MapperProxyFactory會為每個Mapper創建一個MapperProxy代理類,MapperProxy包含了Mapper操作SqlSession所有的細節,因此我們就可以直接使用Mapper的方法就可以跟SqlSession進行交互。
饒了一圈,發現我現在還沒講sql注入器的源碼分析,你不用慌,你得體現出老司機的成熟穩定,之前我也跟你說了sql注入器的原理了,只剩下源碼分析,這時候我們應該在源碼分析之前做足前戲,前戲做足就剩下撕、拉、扯、剝開源碼的外衣了,來不及解釋了快上車!
源碼分析從Mapper的注冊與綁定過程的時序圖看,要想將sql注入器無縫鏈接地添加到mybatis里面,那就得從Mapper注冊步驟添加,果然,mp很雞賊地繼承了MapperRegistry這個類然后重寫了addMapper方法:
com.baomidou.mybatisplus.MybatisMapperRegistry#addMapper:
publicvoid addMapper(Class type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; // throw new BindingException("Type " + type + // " is already known to the MybatisPlusMapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // It"s important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won"t try. // TODO 自定義無 XML 注入 MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
方法中將MapperAnnotationBuilder替換成了自家的MybatisMapperAnnotationBuilder,在這里特別說明一下,mp為了不更改mybatis原有的邏輯,會用繼承或者直接粗暴地將其復制過來,然后在原有的類名上加上前綴“Mybatis”。
com.baomidou.mybatisplus.MybatisMapperAnnotationBuilder#parse:
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); // TODO 注入 CURD 動態 SQL (應該在注解之前注入) if (BaseMapper.class.isAssignableFrom(type)) { GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); } for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
sql注入器就是從這個方法里面添加上去的,首先判斷Mapper是否是BaseMapper的超類或者超接口,BaseMapper是mp的基礎Mapper,里面定義了很多默認的基礎方法,意味著我們一旦使用上mp,通過sql注入器,很多基礎的數據庫操作都可以直接繼承BaseMapper實現了,開發效率爆棚有木有!
com.baomidou.mybatisplus.toolkit.GlobalConfigUtils#getSqlInjector:
public static ISqlInjector getSqlInjector(Configuration configuration) { // fix #140 GlobalConfiguration globalConfiguration = getGlobalConfig(configuration); ISqlInjector sqlInjector = globalConfiguration.getSqlInjector(); if (sqlInjector == null) { sqlInjector = new AutoSqlInjector(); globalConfiguration.setSqlInjector(sqlInjector); } return sqlInjector; }
GlobalConfiguration是mp的全局緩存類,用于存放mp自帶的一些功能,很明顯,sql注入器就存放在GlobalConfiguration中。
這個方法是先從全局緩存類中獲取自定義的sql注入器,如果在GlobalConfiguration中沒有找到自定義sql注入器,就會設置一個mp默認的sql注入器AutoSqlInjector。
sql注入器接口:
// SQL 自動注入器接口 public interface ISqlInjector { // 根據mapperClass注入SQL void inject(MapperBuilderAssistant builderAssistant, Class> mapperClass); // 檢查SQL是否注入(已經注入過不再注入) void inspectInject(MapperBuilderAssistant builderAssistant, Class> mapperClass); // 注入SqlRunner相關 void injectSqlRunner(Configuration configuration); }
所有自定義的sql注入器都需要實現ISqlInjector接口,mp已經為我們默認實現了一些基礎的注入器:
com.baomidou.mybatisplus.mapper.AutoSqlInjector
com.baomidou.mybatisplus.mapper.LogicSqlInjector
其中AutoSqlInjector提供了最基本的sql注入,以及一些通用的sql注入與拼裝的邏輯,LogicSqlInjector在AutoSqlInjector的基礎上復寫了刪除邏輯,因為我們的數據庫的數據刪除實質上是軟刪除,并不是真正的刪除。
com.baomidou.mybatisplus.mapper.AutoSqlInjector#inspectInject:
public void inspectInject(MapperBuilderAssistant builderAssistant, Class> mapperClass) { String className = mapperClass.toString(); SetmapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { inject(builderAssistant, mapperClass); mapperRegistryCache.add(className); } }
該方法是sql注入器的入口,在入口處添加了注入過后不再注入的判斷功能。
// 注入單點 crudSql @Override public void inject(MapperBuilderAssistant builderAssistant, Class> mapperClass) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); // 駝峰設置 PLUS 配置 > 原始配置 GlobalConfiguration globalCache = this.getGlobalConfig(); if (!globalCache.isDbColumnUnderline()) { globalCache.setDbColumnUnderline(configuration.isMapUnderscoreToCamelCase()); } Class> modelClass = extractModelClass(mapperClass); if (null != modelClass) { // 初始化 SQL 解析 if (globalCache.isSqlParserCache()) { PluginUtils.initSqlParserInfoCache(mapperClass); } TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass); injectSql(builderAssistant, mapperClass, modelClass, table); } }
注入之前先將Mapper類提取泛型模型,因為繼承BaseMapper需要將Mapper對應的model添加到泛型里面,這時候我們需要將其提取出來,提取出來后還需要將其初始化成一個TableInfo對象,TableInfo存儲了數據庫對應的model所有的信息,包括表主鍵ID類型、表名稱、表字段信息列表等等信息,這些信息通過反射獲取。
com.baomidou.mybatisplus.mapper.AutoSqlInjector#injectSql:
protected void injectSql(MapperBuilderAssistant builderAssistant, Class> mapperClass, Class> modelClass, TableInfo table) { if (StringUtils.isNotEmpty(table.getKeyProperty())) { /** 刪除 */ this.injectDeleteByIdSql(false, mapperClass, modelClass, table); /** 修改 */ this.injectUpdateByIdSql(true, mapperClass, modelClass, table); /** 查詢 */ this.injectSelectByIdSql(false, mapperClass, modelClass, table); } /** 自定義方法 */ this.inject(configuration, builderAssistant, mapperClass, modelClass, table); }
所有需要注入的sql都是通過該方法進行調用,AutoSqlInjector還提供了一個inject方法,自定義sql注入器時,繼承AutoSqlInjector,實現該方法就行了。
com.baomidou.mybatisplus.mapper.AutoSqlInjector#injectDeleteByIdSql:
protected void injectSelectByIdSql(boolean batch, Class> mapperClass, Class> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID; SqlSource sqlSource; if (batch) { sqlMethod = SqlMethod.SELECT_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append(""); ids.append("#{item}"); ids.append(" "); sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), ids.toString()), modelClass); } else { sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(), table.getKeyColumn(), table.getKeyProperty()), Object.class); } this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table); }
我隨機選擇一個刪除sql的注入,其它sql注入都是類似這么寫,SqlMethod是一個枚舉類,里面存儲了所有自動注入的sql與方法名,如果是批量操作,SqlMethod的定義的sql語句在添加批量操作的語句。再根據table和sql信息創建一個SqlSource對象。
com.baomidou.mybatisplus.mapper.AutoSqlInjector#addMappedStatement:
public MappedStatement addMappedStatement(Class> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class> parameterClass, String resultMap, Class> resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { // MappedStatement是否存在 String statementName = mapperClass.getName() + "." + id; if (hasMappedStatement(statementName)) { System.err.println("{" + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL."); return null; } /** 緩存邏輯處理 */ boolean isSelect = false; if (sqlCommandType == SqlCommandType.SELECT) { isSelect = true; } return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null, parameterClass, resultMap, resultType, null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, configuration.getDatabaseId(), languageDriver, null); }
sql注入器的最終操作,這里會判斷MappedStatement是否存在,這個判斷是有原因的,它會防止重復注入,如果你的Mapper方法已經在Mybatis的邏輯里面注冊了,mp不會再次注入。最后調用MapperBuilderAssistant助手類的addMappedStatement方法執行注冊操作。
到這里,一個sql自動注入器的源碼就分析完了,其實實現起來很簡單,因為它利用了Mybatis的機制,站在巨人的肩膀上進行創新。
我希望在你們今后的職業生涯里,不要只做一個只會調用API的crud程序員,我們要有一種刨根問底的精神。閱讀源碼很枯燥,但閱讀源碼不僅會讓你知道API底層的實現原理,讓你知其然也知其所以然,還可以開闊你的思維,提升你的架構設計能力,通過閱讀源碼,可以看到大佬們是如何設計一個框架的,為什么會這么設計。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73754.html
摘要:是最流行的關系型數據庫管理系統之一,在應用方面,是最好的,關系數據庫管理系統應用軟件。是一種關系數據庫管理系統,關系數據庫將數據保存在不同的表中,而不是將所有數據放在一個大倉庫內,這樣就增加了速度并提高了靈活性。 本章主要是對MyBatis-Plus的初步介紹,包括一些背景知識、環境搭建、初步使用等知識和例子。對于背景知識,主要包含對MyBatis-Plus的特性介紹、為什么使用MyB...
摘要:目前只是一個后臺模塊,希望自己技能增強到一定時,可以把的融合進來。目錄第一站,分析了啟動類。看見沒,這個也是配置類,它聲明了視圖解析器地域解析器以及靜態資源的位置,想起來沒,就是前置,后置。程序啟動類我們點擊源碼看看。 Guns基于SpringBoot,致力于做更簡潔的后臺管理系統,完美整合springmvc + shiro + 分頁插件PageHelper + 通用Mapper + ...
摘要:申請連接時執行檢測連接是否有效,做了這個配置會降低性能。作者在版本中使用,通過監控界面發現有緩存命中率記錄,該應該是支持。允許和不允許單條語句返回多個數據集取決于驅動需求使用列標簽代替列名稱。需要驅動器支持。將自動映射所有復雜的結果。 項目github地址:https://github.com/5-Ason/aso... 具體可看 ./db/db-mysql 模塊 本文主要實現的是對...
摘要:的作用可以看到,它給我們提供了一些核心的功能代碼生成器和現成的接口以及可以結合的條件構造器使我們的代碼變得足夠優雅,分頁的使用也是相當的方便,以及提供了不同的主鍵生成策略。 簡介 Mybatis-Plus是在Mybatis的基礎上,國人開發的一款持久層框架。 showImg(https://segmentfault.com/img/bVbvFk4?w=2022&h=862); 并且榮獲...
閱讀 2211·2019-08-30 15:54
閱讀 1947·2019-08-30 13:49
閱讀 666·2019-08-29 18:44
閱讀 824·2019-08-29 18:39
閱讀 1104·2019-08-29 15:40
閱讀 1524·2019-08-29 12:56
閱讀 3134·2019-08-26 11:39
閱讀 3094·2019-08-26 11:37