1. 程式人生 > >Mybatis框架(9)---Mybatis自定義外掛生成雪花ID做為表主鍵專案

Mybatis框架(9)---Mybatis自定義外掛生成雪花ID做為表主鍵專案

Mybatis自定義外掛生成雪花ID做為主鍵專案

先附上專案專案GitHub地址 spring-boot-mybatis-interceptor

有關Mybatis雪花ID主鍵外掛前面寫了兩篇部落格作為該專案落地的鋪墊。

1、Mybatis框架---Mybatis外掛原理

2、java演算法---靜態內部類實現雪花演算法

該外掛專案可以直接運用於實際開發中,作為分散式資料庫表主鍵ID使用。

一、專案概述

1、專案背景

在生成表主鍵ID時,我們可以考慮主鍵自增 或者 UUID,但它們都有很明顯的缺點

主鍵自增:1、自增ID容易被爬蟲遍歷資料。2、分表分庫會有ID衝突。

UUID: 1、太長,並且有索引碎片,索引多佔用空間的問題 2、無序。

雪花演算法就很適合在分散式場景下生成唯一ID,它既可以保證唯一又可以排序,該外掛專案的原理是

通過攔截器攔截Mybatis的insert語句,通過自定義註解獲取到主鍵,併為該主鍵賦值雪花ID,插入資料庫中。

2、技術架構

專案總體技術選型

SpringBoot2.1.7 + Mybatis + Maven3.5.4 + Mysql + lombok(外掛)

3、使用方式

在你需要做為主鍵的屬性上新增@AutoId註解,那麼通過外掛可以自動為該屬性賦值主鍵ID。

public class TabUser {
    /**
     * id(新增自定義註解)
     */
    @AutoId
    private Long id;
    /**
     * 姓名
     */
    private String name;
  //其它屬性 包括get,set方法
}

4、專案測試

配置好資料庫連線資訊,直接啟動Springboot啟動類Application.java,訪問localhost:8080/save-foreach-user就可以看到資料庫資料已經有雪花ID了。

如圖


二、專案程式碼說明

在正式環境中只要涉及到插入資料的操作都被該外掛攔截,併發量會很大。所以該外掛程式碼即要保證執行緒安全又要保證高可用。所以在程式碼設計上做一些說明。

1、執行緒安全

這裡的執行緒安全主要是考慮產生雪花ID的時候必須是執行緒安全的,不能出現同一臺伺服器同一時刻出現了相同的雪花ID,這裡是通過

靜態內部類單例模式 + synchronized

來保證執行緒安全的,具體有關生成雪花ID的程式碼這裡就不貼上。

2、高可用

我們去思考消耗效能比較大的地方可能出要出現在兩個地方

1)雪花演算法生成雪花ID的過程。
2)通過類的反射機制找到哪些屬性帶有@AutoId註解的過程。

第一點

其實在靜態內部類實現雪花演算法這篇部落格已經簡單測試過,生成20萬條資料,大約在1.7秒能滿足實際開發中我們的需要。

第二點

這裡是有比較好的解決方案的,可以通過兩點去改善它。

1)、在外掛中添加了一個Map處理器

   /**
     * key值為Class物件 value可以理解成是該類帶有AutoId註解的屬性,只不過對屬性封裝了一層。
     * 它是非常能夠提高效能的處理器 它的作用就是不用每一次一個物件經來都要看下它的哪些屬性帶有AutoId註解
     * 畢竟類的反射在效能上並不友好。只要key包含該Class,那麼下次同樣的class進來,就不需要檢查它哪些屬性帶AutoId註解。
     */
    private Map<Class, List<Handler>> handlerMap = new ConcurrentHashMap<>();

外掛部分原始碼

public class AutoIdInterceptor implements Interceptor {
   /**
     * Map處理器
     */
    private Map<Class, List<Handler>> handlerMap = new ConcurrentHashMap<>();
    /**
     * 某某方法
     */
    private void process(Object object) throws Throwable {
        Class handlerKey = object.getClass();
        List<Handler> handlerList = handlerMap.get(handlerKey);
        //先判斷handlerMap是否已存在該class,不存在先找到該class有哪些屬性帶有@AutoId
        if (handlerList == null) {
                handlerMap.put(handlerKey, handlerList = new ArrayList<>());
                // 通過反射 獲取帶有AutoId註解的所有屬性欄位,並放入到handlerMap中
        }
         //為帶有@AutoId賦值ID
        for (Handler handler : handlerList) {
            handler.accept(object);
        }
      }
    }

2)新增break label(標籤)

這個就比較細節了,因為上面的process方法不是執行緒安全的,也就是說可能存在同一時刻有N個執行緒進入process方法,那麼這裡可以優化如下:

      //添加了SYNC標籤
        SYNC:
        if (handlerList == null) {
          //此時handlerList確實為null,進入這裡
            synchronized (this) {
                handlerList = handlerMap.get(handlerKey);
          //但到這裡發現它已經不是為null了,因為可能被其它執行緒往map中插入資料,那說明其實不需要在執行下面的邏輯了,直接跳出if體的SYNC標籤位置。
          //那麼也就不會執行 if (handlerList == null) {}裡面的邏輯。
                if (handlerList != null) {
                    break SYNC;
                }
            }
        }

這裡雖然很細節,但也是有必要的,畢竟這裡併發量很大,這樣設計能一定程度提升效能。




 我相信,無論今後的道路多麼坎坷,只要抓住今天,遲早會在奮鬥中嚐到人生的甘甜。抓住人生中的一分一秒,勝過虛度中的一月一年!(7)

<