Mybatis框架(9)---Mybatis自定義插件生成雪花ID作爲表主鍵項目

#<center> Mybatis自定義插件生成雪花ID作爲主鍵項目</center>html

先附上項目項目GitHub地址 spring-boot-mybatis-interceptorjava

有關Mybatis雪花ID主鍵插件前面寫了兩篇博客做爲該項目落地的鋪墊。git

一、Mybatis框架---Mybatis插件原理github

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

該插件項目能夠直接運用於實際開發中,做爲分佈式數據庫表主鍵ID使用。spring

##<font color=#FFD700> 1、項目概述</font>sql

一、項目背景

在生成表主鍵ID時,咱們能夠考慮主鍵自增 或者 UUID,但它們都有很明顯的缺點數據庫

主鍵自增一、自增ID容易被爬蟲遍歷數據。二、分表分庫會有ID衝突。安全

UUID: 一、太長,而且有索引碎片,索引多佔用空間的問題 二、無序。服務器

雪花算法就很適合在分佈式場景下生成惟一ID,它既能夠保證惟一又能夠排序,該插件項目的原理是

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

二、技術架構

項目整體技術選型

SpringBoot2.1.7 + Mybatis + Maven3.5.4 + Mysql + lombok(插件)

三、使用方式

在你須要作爲主鍵的屬性上添加@AutoId註解,那麼經過插件能夠自動爲該屬性賦值主鍵ID。

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

四、項目測試

配置好數據庫鏈接信息,直接啓動Springboot啓動類Application.java,訪問localhost:8080/save-foreach-user就能夠看到數據庫數據已經有雪花ID了。

如圖

<br>

<font color=#FFD700> 2、項目代碼說明</font>

在正式環境中只要涉及到插入數據的操做都被該插件攔截,併發量會很大。因此該插件代碼即要保證線程安全又要保證高可用。因此在代碼設計上作一些說明。

一、線程安全

這裏的線程安全主要是考慮產生雪花ID的時候必須是線程安全的,不能出現同一臺服務器同一時刻出現了相同的雪花ID,這裏是經過

靜態內部類單例模式 + synchronized

來保證線程安全的,具體有關生成雪花ID的代碼這裏就不粘貼。

二、高可用

咱們去思考消耗性能比較大的地方可能出要出如今兩個地方

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;
                }
            }
        }

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

<br> <br>

我相信,不管從此的道路多麼坎坷,只要抓住今天,早晚會在奮鬥中嚐到人生的甘甜。抓住人生中的一分一秒,賽過虛度中的一月一年!(7)

<br>

相關文章
相關標籤/搜索