淘寶網網絡記事本 api,網絡記事本cc

本篇文章給大傢談談網絡記事本 api,以及網絡記事本cc的知識點,希望對各位有所幫助,不要忘了收藏本站喔。

文章詳情介紹:

sagacity-sqltoy 緩存翻譯功能整理,源碼拆解

sagacity-sqltoy 緩存翻譯功能整理,源碼拆解

1.ORM 框架之 sqltoy

今天介紹壹個讓我覺得很特別、用起來特別舒服的 ORM 框架:sagacity-sqltoy,簡稱:sqltoy,這個框架完全國產,框架作者也是中國人。

作者也壹直在推廣該框架,讓更多人了解 sqltoy,sqltoy 的文檔完善,對開發者友好,上手特別簡單,我寫壹些自己玩的項目時,就用的 sqltoy 框架作為持久層框架,也是我選擇 ORM 框架的首選。

這個框架我還和同事吐槽過,說框架太智能了,用了這個框架之後,我都不會使用 Mybatis 了,可想而知,這個框架有多優秀,推薦所有人可以學習壹下,哪怕妳不使用,學習壹下這個框架的優點和特別的地方,也能學到很多。

2.sqltoy 是個什麽框架

Github 開源地址:
https://github.com/chenrenfei/sagacity-sqltoy

Gitee 開源地址:
https://gitee.com/sagacity/sagacity-sqltoy

在線文檔:
https://chenrenfei.github.io/sqltoy/#/

關於 sqltoy 的介紹我從開源倉庫上截取了壹部分

2.1 sqltoy-orm是什麽 sqltoy-orm是比hibernate+myBatis更加貼合項目的orm框架,具有hibernate增刪改和對象加載的便捷性同時也具有比myBatis更加靈活優雅的自定義sql查詢功能。  支持以下數據庫: - oracle 從oracle11g到19c - db2 9.5+,建議從10.5 開始 - mysql 支持5.6、5.7、8.0 版本 - postgresql 支持9.5 以及以上版本 - sqlserver 支持2008到2019版本,建議使用2012或以上版本 - sqlite - DM達夢數據庫 - elasticsearch 隻支持查詢,版本支持5.7+版本,建議使用7.3以上版本 - clickhouse - mongodb (隻支持查詢) - sybase_iq 支持15.4以上版本,建議使用16版本 2.2 是否重復造輪子,我隻想首先說五個特性: 2.2.1 根本上杜絕了sql註入問題,sql支持寫註釋、sql文件動態更新檢測,開發時sql變更會自動重載 2.2.2 最直觀的sql編寫模式,當查詢條件稍微復雜壹點的時候就會體現價值,後期變更維護的時候尤為凸顯 2.2.3 極為強大的緩存翻譯查詢:巧妙的結合緩存減少查詢語句表關聯,極大簡化sql和提昇性能。 2.2.3 最強大的分頁查詢:很多人第壹次了解到何為快速分頁、分頁優化這種極為巧妙的處理,還有在count語句上的極度優化。 2.2.3 跨數據庫函數方言替換,如:isnull/ifnull/nvl、substr/substring 等不同數據庫 當然這隻是sqltoy其中的五個特點,還有行列轉換(俗稱數據旋轉)、多級分組匯總、統壹樹結構表(如機構)查詢、分庫分表sharding、取隨機記錄、取top記錄、修改並返回記錄、慢sql提醒等這些貼合項目應用的功能, 當妳真正了解上述特點帶來的巨大優勢之後,您就會對中國人創造的sqltoy-orm有了信心! sqltoy-orm 來源於個人親身經歷的無數個項目的總結和思考,尤其是性能優化上不斷的挖掘,至於是不是重復的輪子並不重要,希望能夠幫到大傢

這里的介紹我隻摘取了壹部分,更多的特性介紹可以前往 Github 上查看,下麵我們進入本文的主題:緩存翻譯

3.緩存翻譯功能

緩存翻譯,這是壹個什麽功能?

    通過緩存翻譯: 將 code (編碼)轉化為名稱,無需關聯查詢,極大簡化sql並提昇查詢效率。

    通過緩存名稱模糊匹配: 獲取精準的編碼作為條件,避免關聯like 模糊查詢。

例如 MyBatis:

SELECT  i.staff_id,  i.staff_name,  i.sex_type,  d1.DICT_NAME AS sex_type_name,  i.post,  d2.DICT_NAME AS post_name,  i.create_by,  d3.STAFF_NAME  FROM  sqltoy_staff_info i  LEFT JOIN ( SELECT d.DICT_KEY, d.DICT_NAME FROM sqltoy_dict_detail d WHERE d.DICT_TYPE = "SEX_TYPE" ) d1 ON i.SEX_TYPE = d1.DICT_KEY  LEFT JOIN ( SELECT d.DICT_KEY, d.DICT_NAME FROM sqltoy_dict_detail d WHERE d.DICT_TYPE = "POST_TYPE" ) d2 ON i.post = d2.DICT_KEY  LEFT JOIN ( SELECT info.STAFF_ID, info.STAFF_NAME FROM sqltoy_staff_info info ) d3 ON i.create_by = d3.STAFF_ID  WHERE  i.staff_id = "S0003"

解釋下這個 SQL,我要獲取 S0003 的個人信息,sex_type、post、create_by 是 code 編碼,需要轉化為名稱,方便前端展示。那我就需要去關聯字典表和員工表,造成 SQL 需要進行三次關聯。

而 sqltoy:

隻需要在 xml 中的 sql 語句上配置 translate 緩存翻譯功能就行了,code 編碼則會自動轉化為名稱。

是不是簡化了 sql,至於效率也不用擔心,首先在 sql 層麵省去了多表關聯,code 翻譯的結果值是從緩存中獲取,效率更高。總的來看,提高了效率、簡化了 sql 的復雜性,十分方便。

看完了案例,我們就來仔細看看關於緩存翻譯的配置使用,以及作者沒在文檔中詳細說的另外兩種方式(service、rest)的緩存翻譯。

3.1.緩存翻譯初始化

緩存翻譯的功能實現其實並不復雜,通過閱讀源碼能了解到緩存翻譯的加載和使用。

例如:需要獲取員工信息,順便把員工的性別編碼進行翻譯,sqltoy 的流程如下:

    根據業務 sql 去查詢員工信息。

    jdbc 查詢的結果值進行 aop 過濾,判斷是否需要翻譯。

    需要翻譯進入翻譯邏輯,不需要進行結果值封裝,返回結果。

    進入翻譯邏輯,按照翻譯 sql 去查詢字典值。

    業務數據和翻譯數據結果集,進行業務數據的 code 翻譯,替換。

    封裝翻譯之後的結果,返回 service 層。

總結壹下,整個翻譯就是用 Spring AOP 在底層對查詢結果進行統壹替換處理。

下麵我們壹起來看壹下緩存翻譯的加載流程,方便我們後麵理解緩存翻譯使用,這里建議大傢去 Github 上把源碼 clone 下來,打斷點調兩遍,會理解的更加透徹。

3.1.1 加載入口


org.sagacity.sqltoy.SqlToyContext 文件就是 sqltoy 框架加載主入口,這里加載的配置有:sqlToy 配置解析插件、實體對象管理器、翻譯器插件、緩存管理器、統壹公共字段賦值處理、延時檢測時長、數據庫方言參數、es的地址配置等等。然後翻譯器插件就是緩存翻譯。

在 SqlToyContext.initialize 初始化方法中找到翻譯器的初始化方法,點進去。

這個方法的主要目的就是:配置緩存翻譯、緩存路徑、載入具體的緩存翻譯配置。


TranslateConfigParse.parseTranslateConfig 方法才是真正的緩存翻譯文件的內容解析。

由於方法內容過長,不好截圖,我簡單概述下方法所做的事,具體內容大傢可以通過源碼進行了解。

parseTranslateConfig 主要功能有:設置緩存的存儲地址、內存大小、過期時間、sql 語句加載、sql 語句參數加載、數據源以及增量緩存的刷新時間等。用大白話來說就是,把緩存 xml 文件中的內容和配置進行解析,加載到 SqlToyConfig 實例里,方便後續的使用。

到這里,緩存翻譯的初始化加載流程就是介紹完了,介紹起來幾句話就講完了,但實際的加載流程,建議大傢去看看源碼。

3.2 三種緩存翻譯方式

下麵我們就來看看緩存翻譯的具體使用方法,先說明,本篇文章的緩存翻譯,隻是 cache-translates 部分,沒有 cache-update-checkers 部分

cache-translates負責將數據加載到緩存

cache-update-checkers則負責檢查數據是否發生變化清理緩存,當下次使用緩存時會自動重新獲取數據放入緩存,從而實現緩存的刷新

3.2.1 sql 緩存翻譯

sql 類型的緩存翻譯,是最基本的使用,也是作者在文檔中公開的使用方式,使用的方式也最簡單、廣泛,適用於絕大部分翻譯場景。

sql 類型緩存翻譯案例走起。

第壹步,先在緩存翻譯的 xml 中,書寫好緩存值的 sql 語句。例如我要緩存員工的姓名,員工ID作為緩存 key,姓名作為 value,sql 如下:

          

cache 是緩存名稱,名稱必須要唯壹(必填),datasource 是當前數據庫數據源(非必填)。

第二步,在需要翻譯的業務 sql 語句上,配置緩存翻譯功能,並指定使用的緩存名稱、需要翻譯的 key 值。例如:

                   

translate 可配置的屬性列表:

cache:具體的緩存定義的名稱

cache-type:壹般針對數據字典,提供壹個分類條件過濾

columns:sql中的查詢字段名稱,可以逗號分隔對多個字段進行翻譯

cache-indexs:緩存數據名稱對應的列,不填則默認為第二列(從0開始,1則表示第二列),例如緩存的數據結構是:key、name、fullName,則第三列表示全稱

然後我們測試壹下結果:

service 層,調用上麵的業務 sql(getStaffInfoByStaffId):

  /**   * 根據 ID 獲取 vo   *   * @param staffId   * @return   */   @Override   public SqltoyStaffInfoVO getStaffInfoByStaffId(String staffId) {     return this.sqlToyLazyDao.loadBySql("getStaffInfoByStaffId", new String[]{"staffId"}, new String[]{staffId}, SqltoyStaffInfoVO.class);   }

test 層:

@Test void testFive() {    SqltoyStaffInfoVO vo = this.passwordService.getStaffInfoByStaffId("S0001");    Assert.assertEquals("測試失敗-性別",vo.getSexType(),"男");    Assert.assertEquals("測試失敗-職位類別",vo.getPost(),"管理崗");    Assert.assertEquals("測試失敗-職位等級",vo.getPostGrade(),"L10");    System.out.printf("性別:%s,職位:%s,職位等級:%s",vo.getSexType(),vo.getPost(),vo.getPostGrade()); }

測試結果:

可以看到,斷言測試通過,並且打印的日誌顯示,翻譯的結果成功,成功把性別、職位類別、職位等級等編碼翻譯為中文。

3.2.2 service 緩存翻譯

service 類型的緩存翻譯,作者隻是在 sqltoy-starter-showcase 模塊項目中的 sqltoy-translate.xml 文件中提到過,具體的案例,在項目中我沒有找到,以為功能沒實現,作者說實現了,就自己研究了壹下,測試了壹遍翻譯功能。

service 類型的緩存翻譯我壹開始認為有點多余,我的想法是,都有 sql 類型的緩存翻譯了,幹嘛多此壹舉弄壹個 service,並且 service 的緩存翻譯最終還是用 sql 獲取數據。

仔細思考過後,我發現自己錯了,存在就是合理的,我認為沒有、多余,隻是我沒有使用場景,而作者開發 service 類型,肯定就是有使用場景的。

思考壹番過後,說下我認為 service 類型的使用場景,在微服務係統中,A、B 兩個係統是分開的,分別使用各自的庫 A 庫和 B 庫,這個時候,我在 A 係統中查詢壹些業務數據,其中某個字段的編碼所對應的 value 值並不在 A 庫中,而是 B 庫,這種場景下,數據是跨庫的,無法關聯查詢,隻能在業務代碼中進行 B 庫數據查詢,進行編碼轉換。

而 service 類型的緩存翻譯,則可以很順利的解決這個問題,在 A 係統中通過 Fegin 調用 B 係統的 API,形成壹個 service 方法,然後 A 係統在業務 sql 使用 service 類型的緩存翻譯,就可以翻譯 sql 中的編碼了。

說起來有點繞,我們案例整起:

我這里沒有開兩個服務,而是在 service 里新寫壹個方法,然後在業務 sql 中調用這個方法,模擬跨服務的場景。

第壹步,先把緩存方法書寫好。依然是把員工ID翻譯為員工姓名。

    /**      * 獲取所有員工信息記錄      *      * @return      */     @Override     public List queryStaffInof() {         List findStaffInof = this.sqlToyLazyDao.findBySql("findStaffInof", new SqltoyStaffInfoVO());         List list = new ArrayList<>(findStaffInof.size());         findStaffInof.stream().forEach(e -> {             Object[] arr = new Object[]{e.getStaffId(),e.getStaffName()};             list.add(arr);         });         return list;     }

這個方法可以看作是 A 係統中通過 Fegin 調用 B 係統的 API 返回的值。

方法返回類型是:List,這個是緩存翻譯底層的限制,返回類型可以是:List>、List

找到對應的源碼可以看到限制:

private static HashMap wrapCacheResult(Object target, TranslateConfigModel cacheModel) {         if (target == null) {             return null;         } else if (target instanceof HashMap && ((HashMap)target).isEmpty()) {             return null;         } else if (target instanceof HashMap && ((HashMap)target).values().iterator().next().getClass().isArray()) {             return (HashMap)target;         } else {             LinkedHashMap result = new LinkedHashMap();             Object[] row;             if (target instanceof HashMap) {                 if (!((HashMap)target).isEmpty()) {                     Iterator iter;                     Entry entry;                     if (((HashMap)target).values().iterator().next() instanceof List) {                         iter = ((HashMap)target).entrySet().iterator();                         while(iter.hasNext()) {                             entry = (Entry)iter.next();                             row = new Object[((List)entry.getValue()).size()];                             ((List)entry.getValue()).toArray(row);                             result.put(entry.getKey(), row);                         }                     } else {                         iter = ((HashMap)target).entrySet().iterator();                         while(iter.hasNext()) {                             entry = (Entry)iter.next();                             result.put(entry.getKey(), new Object[]{entry.getKey(), entry.getValue()});                         }                     }                 }             } else if (target instanceof List) {                 List tempList = (List)target;                 if (!tempList.isEmpty()) {                     int cacheIndex = cacheModel.getKeyIndex();                     int i;                     int n;                     List dataSet;                     if (tempList.get(0) instanceof List) {                         i = 0;                         for(n = tempList.size(); i < n; ++i) {                             dataSet = (List)tempList.get(i);                             Object[] rowAry = new Object[dataSet.size()];                             dataSet.toArray(rowAry);                             result.put(rowAry[cacheIndex].toString(), rowAry);                         }                     } else if (tempList.get(0) instanceof Object[]) {                         i = 0;                         for(n = tempList.size(); i < n; ++i) {                             row = (Object[])((Object[])tempList.get(i));                             result.put(row[cacheIndex].toString(), row);                         }                     } else if (cacheModel.getProperties() != null && cacheModel.getProperties().length > 1) {                         dataSet = BeanUtil.reflectBeansToInnerAry(tempList, cacheModel.getProperties(), (Object[])null, (ReflectPropertyHandler)null, false, 0);                         Iterator var12 = dataSet.iterator();                         while(var12.hasNext()) {                             Object[] row = (Object[])var12.next();                             result.put(row[cacheIndex].toString(), row);                         }                     }                 }             }             return result;         }     }

wrapCacheResult 方法的參數:

target 就是 queryStaffInof() 方法(可以理解為 B 係統的方法)的返回值。

TranslateConfigModel 是緩存翻譯的模型 Bean,Bean 裏面有緩存翻譯的相關基礎屬性,例如: 緩存類型(sql,service,rest),數據源,緩存名稱,自定義的 ServiceBean,自定義的 ServiceMethod,rest 類型的 url 等等。

第二步,在 A 係統中的緩存翻譯文件中,配置 service 類型的緩存,方法有參,無參,在緩存的 xml 中沒去區別。。

service service 類的路徑

method 具體調用方法

cache 緩存名稱

第三步,A 係統的業務 sql 上配置 service 緩存。這里和 sql 類型的使用方式是壹模壹樣的,service 方法如果有參數,也是通過 cache-type 屬性傳入。唯壹不壹樣的地方就是 cache 改為 servcie 的緩存名稱。

                   

然後我們測試壹下結果,調用的 service 層方法不變,隻是把 service 對應的業務 sql 上的緩存由 sql 類型換為 servcie 類型。

test 層

@Test void testEight() {   SqltoyStaffInfoVO vo = this.passwordService.getStaffInfoByStaffId("S0001");   Assert.assertEquals("測試失敗-創建人姓名",vo.getCreateBy(),"張三");   System.out.printf("創建人姓名:%s",vo.getCreateBy()); }

測試結果:

斷言測試通過,並且打印的日誌顯示,翻譯的結果成功,成功把創建人ID翻譯為中文名稱。

我補充壹下,service 緩存是如何通過妳配置的 service 和 method 就獲取到緩存數據的?其實是通過配置的 service 反射調用 method 來獲取數據的,這點可以在源碼中找到。

這是緩存翻譯的三種類型判斷,根據類型調用不同的緩存數據獲取方法,我們進入 service 類型看看,看下底層是不是反射。


TranslateFactory.getServiceCacheData() ->
SqlToyContext.getServiceData() -> BeanUtil.invokeMethod() -> Method.invoke()。

從 servcie 類型調用 getServiceCacheData() 壹直往下,會走到 Method.invoke(),可以證明 servcie 類型緩存翻譯方法調用方式通過反射來進行的。

3.2.3 rest 緩存翻譯

rest 緩存翻譯,牠和 servcie、sql 類型都不壹樣,rest 緩存翻譯是通過 url 地址向第三方服務發起請求,獲取所需要的緩存值或字典數據。

前麵 service 緩存翻譯可以跨服務,從 A 服務調用 B 服務的數據,而 rest 則可以跨係統,從 A 係統調用 B 係統的數據(當然, service 也可以做到,在本地 servcie 層通過 HttpClient 調用第三方服務和通過 Fegin 調用其他服務都是壹樣的)。

說下我認為 rest 緩存翻譯的使用場景,假設我公司有兩個單體應用 A 和 B,A 和 B 各自有各自的服務器、數據庫、nginx,如果 A 需要調用 B 的數據字典,來翻譯自己的數據里的某個字段。

這個場景用 service 也能做到,不過需要自己去寫 HttpClient 部分的代碼,而 rest 則在底層幫用戶做好了,隻需要提供調用 url 就行。

如果調用的 B 係統接口還有用戶身份驗證,也可以配置壹個賬號,進行請求認證。

說了這麽多,人都整懵了,我們案例走起。

第壹步,在緩存文件中,配置 rest 緩存。

url 就是妳請求的第三方係統地址 (必填)

cache 緩存名稱 (必填)

username 身份認證的用戶名 (非必填)

password 身份認證的密碼 (非必填)

用戶名和密碼兩個屬性,看請求的接口,接口需要進行身份認證,就需要加上,不需要認證,則可以沒有。

其實到這里,rest 的緩存就配置好了,很簡單的壹個配置,使用就直接在業務 sql 上配置緩存,指定使用緩存名稱為第壹步的緩存名稱就行了,和使用 sql 類型、service 類型的緩存翻譯方式沒有區別。

下麵通過案例和 rest 類型的源碼,來幫助大傢更好的理解 rest 類型的原理和身份認證這部分,以及如果是帶參數請求,第三方的接口如何接收參數。

這本地寫了壹個案例,翻譯員工的崗位,用的緩存翻譯,就是第壹步中配置的 rest 翻譯,帶了壹個字典編碼作為參數。

然後我們看下 sqltoy 底層是如何調用第三方接口的的。

在 TranslateFactory 類下有 getCacheData 方法

getCacheData 方法是根據不同的緩存類型調用對應的方法獲取緩存數據,然後返回上層,進行翻譯值的替換。

我們進入 rest 類型的方法看看。

重點看下 332 行,這壹行,拿到了請求的 url、username、password、請求參數 Key、請求參數 Value 等信息,通過封裝的 HttpClient 進行發起了接口請求。

332 行之後的代碼就是對接口響應的數據進行封裝處理,把 String 字符串轉為 JSON 格式,再轉為 List 返回給上層調用方法。

我們繼續進入封裝的 doPost 方法看看。

重點看兩個地方,75 -79 行,這里是設置身份認證的地方,另壹個就是 84 - 92 行,這部分是設置請求參數,請求參數的封裝用的是:UrlEncodedFormEntity,body 參數格式會轉為“KEY1=VALUE1&KEY2=VALUE2&...”這種形式,服務端接收以後也要依據這種協議形式做處理。

好了,rest 類型的底層說完了,壹起來看下服務端的代碼,以及接收參數的處理。

在方法的第壹行,是參數轉換處理方法,第二行才是服務端的業務邏輯代碼。

由於參數是通過 UrlEncodedFormEntity 方式傳遞的,我通過 HttpServletRequest 讀取字符流,然後轉為字符串,根據 = 號切割,獲取 value 值,這個 value 值就是 rest 請求帶過來字典參數。

整個 rest 配置流程和底層源碼都講完了,我們測試壹遍,看看翻譯功能對不對。我這里的測試,是把測試的服務,打成 jar 包,用 8082 端口啟動,模擬 A、B 兩個單體應用,下麵是我的測試結果。

可以看到,(8080 A 係統)本地調用 servcie 方法獲取員工信息,斷言的結果是正確通過的,IDEA 控制臺打印的日誌顯示員工崗位翻譯成功。

然後在 (8082 B 係統)的日誌上,能看到獲取的參數 Value 和查詢 sql 日誌。

4.緩存翻譯底層 Ehcache

這壹節,主要說下緩存翻譯的底層緩存和壹些源碼上的內容。

4.1 Ehcache

緩存翻譯的緩存,底層實現是:Ehcache,這點在框架的源碼能找到,源碼位置在:
org.sagacity.sqltoy.translate.cache 包下麵。

紅色部分是緩存翻譯相關的文件,包括:解析緩存翻譯的配置、定時檢測緩存是否更新程序、緩存刷新檢測、緩存翻譯器、緩存相關 model、緩存實現等。

綠色部分就是緩存的實現,TranslateCacheManager 是抽象類,提供緩存接口規範。

TranslateEhcacheManager 是具體緩存實現類,繼承抽象類,實現緩存具體功能。

從實現類中我們可以看到,實現類用的緩存是 CacheManager,而 CacheManager 就是 Ehcache 的緩存管理器。

在實現類重寫的 init 方法可以看到,如果 CacheManager 是 null,就為 CacheManager 創建壹個實例,提供給其他方法(put、getCache)使用。

4.2 緩存翻譯的 AOP 原理

3.1.緩存翻譯初始化 節我講了,緩存翻譯的工作流程,但隻是進行文字描述,沒用過這個框架的朋友可能會有點懵,這壹小節,我通過源碼來仔細梳理緩存翻譯的工作流程。

緩存翻譯是如何獲取值的?又是如何把 code 編碼替換為中文名稱的?又是如何在翻譯之後把結果集返回給我們的 service 層?這壹小節具體解決這三個問題。

4.2.1 AOP 處理

我們從業務邏輯代碼 servcie 層調用的 SqlToyLazyDao.loadBySql 方法進去,壹直往下找:
SqlToyDaoSupport.loadBySql ->
SqlToyDaoSupport.loadByQuery。

第壹行的 SqlToyConfig 是壹個 sql 解析對象,裏面有 sql 語句屬性、參數 key、參數 value、數據庫方言、翻譯器、脫敏配置等,其實就是 sql.xml 解析出來的對象,裏面包含當前 sql 的各種配置。

第二行就是拿到 sqltoyCofing 實例,去查詢數據以及根據實例里的配置,去解析 sql 的轉換、解析、翻譯、脫敏、行轉列的操作。進入 findByQuery 方法,看裏面的具體操作。

方法的 750 - 755 行,操作的是把 sql 中的參數 key 替換為占位符號,並且把參數 values 按照順序整理到 queryParam 實例中。

queryParam 的 sql 就是壹個完整的 sql,例如:sql select id,name,age from users u where u.name = ? and u.age ?

而 paramsValue 就是 ?的數組 value :["java",21]。

然後我們看具體的調用 findBySql 方法,我本地用的 Mysql 數據庫,所以我進入的是 Mysql 的實現方法。

壹直往下找:MySqlDialect.findBySql -> DialectUtils.findBySql。

現在我們到底層了,相信讀者對我標記出來的代碼不陌生,JDBC 代碼而已,根據 sql 查詢數據,然後看 222 行,這里很重要,框架在這里把 JDBC 查詢出來的實例 rs 進行了結果值封裝、替換,然後把封裝、替換之後的 rs 結果集返回給 service 層。

對這個功能有沒有很熟悉?像不像 Spring AOP 切麵處理?其實就是 AOP 原理,本來我沒意識到是 AOP,後麵在 sqltoy 的 QQ 群里,作者提了壹句,我才意識到。

到這里就能知道我們的結果集為什麽會進行 code 編碼翻譯,翻譯為中文名稱,因為框架底層對 rs(ResultSet) 集合進行了替換

然後我們繼續看緩存翻譯是如何獲取值的?又是如何把 code 編碼進行替換的?

4.2.2 緩存翻譯是如何獲取值的?又是如何把 code 編碼進行替換的?

點擊 222 行的
ResultUtils.processResultSet() 方法。

再進入 104 行的 getResultSet() 方法。

看到 288 行,這里通過
sqlToyConfig.getTranslateMap() 方法獲取當前運行的 sql 是否有解析到緩存翻譯器。

如果妳在業務 sql 上配置了 就有緩存翻譯器,沒配置就代表不需要翻譯,就沒有緩存翻譯器。

然後看到 289 行,如果有緩存翻譯器,就獲取翻譯器,並在 292 根據翻譯器獲取緩存數據,我們進入到
sqlToyContext.getTranslateManager().getTranslates() 方法看看。

方法 139 行在循環翻譯器,然後挨個處理每個翻譯器,141 行,判斷加載的緩存有沒有翻譯器里的緩存名稱,有就把當前翻譯器取出來,通過 143 行的 getCacheData 方法去獲取緩存數據。

175 行,先是根據緩存名稱取緩存數據,如果獲取到的數據為 null 或為空,則通過
TranslateFactory.getCacheData()方法去數據庫獲取數據,查到數據之後,放入緩存( 181 行)。

這就是
TranslateFactory.getCacheData(),根據翻譯器的類型,進入不同的緩存數據加載方法。

這里我們看 sql 類型方法。

283 行獲取緩存上數據源,如果沒獲取到就獲取當前 sql 解析的數據源,然後 288 行判斷緩存有沒有傳入參數,也就是 cache-type 屬性的值,有參數則構造壹個帶條件 QueryExecutor,調用
DialectFactory.getInstance().findByQuery() 方法就又進入了 AOP 小節的講的 findByQuery() 方法。

這個方法是不是有點眼熟?其實就是 service 層調用的底層方法,緩存器調用 findByQuery 方法,通過 JDBC 把緩存器解析的 sql 執行,獲取到翻譯數據。

緩存數據取到了,我們回到 getResultSet(),看到 293 行,緩存數據為 null 或為空,則把 288 行的緩存翻譯器標記賦值為:false。

假設緩存數據不為空,緩存標記為 true,方法壹直往下運行,找到 370 行(我標記的位置),先是判斷緩存標記是否為 true,為真則進入
processResultRowWithTranslate 方法,我們進入該方法。

註意看方法上的註釋:@todo 存在緩存翻譯的結果處理。這就是我們要找的翻譯方法,把 code 編碼替換為中文名稱。

這里我在本地斷點,把方法的運行情況截圖下來,方便大傢理解各個參數的意思和值以及是如何替換的。

labelNames 是業務 sql 上的所有字段,例如業務 sql 為:select id,name from users,那 labelNames = [id,name]

fieldValue 是根據當前 labelNames 獲取的 value值,例如當前 label 為 name,fieldValue 則為:張三,也就是業務 sql 查詢得到的數據。

keyIndex 是方法參數 size( labelNames 的長度 ) 的循環當前值。

然後我們看下 translateKey() 方法,還是斷點調試的截圖。

方法第壹行獲取了需要翻譯的 code 編碼,然後判斷是不是單值翻譯,如果是多個值翻譯,就進入 else 進行切割,然後循環翻譯,我這里是單值。

cacheValues 是根據翻譯的 code 編碼獲取到對應的緩存對象,如果沒有獲取到,則代表沒查到這個 code 編碼的中文信息,打印錯誤日誌。對象存在,就根據翻譯器的下標獲取中文名稱,賦值給 fieldValue 並返回。

這里說壹下 translate.getIndex() 下標問題,下標默認是 1,可以在 更改 cache-indexs 屬性,這個下標為 1 是什麽意思呢?牠代表的就是把 code 編碼替換為緩存 sql 的哪壹個字段。

translateKey() 方法把 code 替換為中文後,返回到
processResultRowWithTranslate() 方法,添加到 rowData(List 集合)實例中,然後逐步返回到 DialectUtils.findBySql(),最終返回到 service 層,被我們獲取。

這就是完整的緩存翻譯流程,從壹開始的業務 sql 查詢,到返回 rs AOP 處理,到判斷是否有翻譯器,到獲取緩存數據,到翻譯 code,再到翻譯完成,返回到 rs AOP 處理位置,返回到 servcie,整個流程,就講完了。

5.結尾

緩存翻譯這個功能,是 sqltoy 框架最吸引我的地方,牠的便捷性、性能、sql 簡化和設計,都讓我著迷。

這里我建議大傢把 sqltoy 的源碼 clone 下來,仔細看看,會看到很多我們用起來沒註意的小細節,能幫助我們更好的理解 sqltoy。

最後,如果我的文章有幫助到大傢或者認為寫的不錯,請分享給更多人,謝謝。

如文章有錯誤地方,歡迎大傢留言或私信(),告知我,感激不盡。