MybatisPlus常用註解

一、@TableName

value屬性

實體類的名字是User,資料庫表名是t_user

@TableName(value = "t_user")
public class User {

二、@TableId

1、雪花演算法

默認情況下資料庫的id列使用的是基於雪花演算法的策略生成

image-20220504223808989

背景

隨著業務規模的不斷擴大,需要選擇合適的方案去應對數據規模的增長,以應對逐漸增長的訪問壓力和數據量。

資料庫的擴展方式主要包括:業務分庫、主從複製,資料庫分表。

資料庫分表

將不同業務數據分散存儲到不同的資料庫伺服器,能夠支撐百萬甚至千萬用戶規模的業務,但如果業務繼續發展,同一業務的單表數據也會達到單台資料庫伺服器的處理瓶頸。例如,淘寶的幾億用戶數據,如果全部存放在一台資料庫伺服器的一張表中,肯定是無法滿足性能要求的,此時就需要對單表數據進行拆分。

單表數據拆分有兩種方式:垂直分表和水平分表。示意圖如下:

img

垂直分表:

  • 垂直分表適合將表中某些不常用且佔了大量空間的列拆分出去。
  • 例如,前面示意圖中的 nickname 和 description 欄位,假設我們是一個婚戀網站,用戶在篩選其他用戶的時候,主要是用 age 和 sex 兩個欄位進行查詢,而 nickname 和 description 兩個欄位主要用於展示,一般不會在業務查詢中用到。description 本身又比較長,因此我們可以將這兩個欄位獨立到另外一張表中,這樣在查詢 age 和 sex 時,就能帶來一定的性能提升。

水平分表:

  • 水平分表適合錶行數特別大的表,有的公司要求單錶行數超過 5000 萬就必須進行分表,這個數字可以作為參考,但並不是絕對標準,關鍵還是要看錶的訪問性能。對於一些比較複雜的表,可能超過 1000 萬就要分表了;而對於一些簡單的表,即使存儲數據超過 1 億行,也可以不分表。
  • 但不管怎樣,當看到表的數據量達到千萬級別時,作為架構師就要警覺起來,因為這很可能是架構的性能瓶頸或者隱患。

水平分表相比垂直分表,會引入更多的複雜性,例如數據id:

主鍵自增:

  • 以最常見的用戶 ID 為例,可以按照 1000000 的範圍大小進行分段,1 ~ 999999 放到表 1中,1000000 ~ 1999999 放到表2中,以此類推。
  • 複雜點:分段大小的選取。分段太小會導致切分後子表數量過多,增加維護複雜度;分段太大可能會導致單表依然存在性能問題,一般建議分段大小在 100 萬至 2000 萬之間,具體需要根據業務選取合適的分段大小。
  • 優點:可以隨著數據的增加平滑地擴充新的表。例如,現在的用戶是 100 萬,如果增加到 1000 萬,只需要增加新的表就可以了,原有的數據不需要動。
  • 缺點:分布不均勻。假如按照 1000 萬來進行分表,有可能某個分段實際存儲的數據量只有 1 條,而另外一個分段實際存儲的數據量有 1000 萬條。

Hash :

  • 同樣以用戶 ID 為例,假如我們一開始就規划了 10 個資料庫表,可以簡單地用 user_id % 10 的值來表示數據所屬的資料庫表編號,ID 為 985 的用戶放到編號為 5 的子表中,ID 為 10086 的用戶放到編號為 6 的子表中。
  • 複雜點:初始表數量的確定。表數量太多維護比較麻煩,表數量太少又可能導致單表性能存在問題。
  • 優點:表分布比較均勻。
  • 缺點:擴充新的表很麻煩,所有數據都要重分布。

*雪花演算法:*

雪花演算法是由Twitter公布的分散式主鍵生成演算法,它能夠保證不同表的主鍵的不重複性,以及相同表的主鍵的有序性。

  • 核心思想:

    • 長度共64bit(一個long型)。
    • 首先是一個符號位,1bit標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0。
    • 41bit時間截(毫秒級),存儲的是時間截的差值(當前時間截 – 開始時間截),結果約等於69.73年。
    • 10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID,可以部署在1024個節點)。
    • 12bit作為毫秒內的流水號(意味著每個節點在每毫秒可以產生 4096 個 ID)。

img

  • 優點:整體上按照時間自增排序,並且整個分散式系統內不會產生ID碰撞,並且效率較高。

2、指定主鍵列

  • 測試:將資料庫表中的id列改為 uid,將實體類中的id屬性改成 uid,執行數據插入,則報告如下錯誤

img

  • 原因:因為MP默認認為id是主鍵列,其他名字的屬性MP無法默認自動填充
  • 解決方案:為主鍵列添加 @TableId 註解

3、value屬性

實體類的屬性名是 id,資料庫的列名是 uid,此時使用 value 屬性將屬性名映射到列名

@TableId(value = "uid")
private String id;

4、type屬性

type屬性用來定義主鍵策略

  • IdType.ASSIGN_ID:使用基於雪花演算法的策略生成數據id
@TableId(type = IdType.ASSIGN_ID)
private Long id;

注意:當對象的id被明確賦值時,不會使用雪花演算法

  • IdType.AUTO:使用資料庫的自增策略
@TableId(type = IdType.AUTO)
private Long id;

注意:該類型請確保資料庫設置了 ID自增 否則無效

  • 全局配置:要想影響所有實體的配置,可以設置全局主鍵配置
#全局設置主鍵生成策略
mybatis-plus.global-config.db-config.id-type=auto

三、@TableField

1、value屬性

功能同TableId的value屬性

注意:MP會自動將資料庫中的下劃線命名風格轉化為實體類中的駝峰命名風格

例如,資料庫中的列 create_time 和 update_time 自動對應實體類中的 createTime 和 updateTime

img

private LocalDateTime createTime;
private LocalDateTime updateTime;

擴展知識:為什麼建議使用你 LocalDateTime ,而不是 Date?//zhuanlan.zhihu.com/p/87555377

  • java.util.Date的大多數方法已經過時
  • java.util.Date的輸出可讀性差
  • java.util.Date對應的格式化類SimpleDateFormat是執行緒不安全的類。阿里巴巴開發手冊中禁用static修飾SimpleDateFormat。
  • LocalDateTime 對應的格式化類DateTimeFormatter是執行緒安全的

2、自動填充

需求描述:

項目中經常會遇到一些數據,每次都使用相同的方式填充,例如記錄的創建時間,更新時間等。我們可以使用MyBatis Plus的自動填充功能,完成這些欄位的賦值工作。

例如,阿里巴巴的開發手冊中建議每個資料庫表必須要有create_time 和 update_time欄位,我們可以使用自動填充功能維護這兩個欄位

  • step1:添加fill屬性
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
  • step2:實現元對象處理器介面 -> 創建handler包,創建MyMetaObjectHandler類

注意:不要忘記添加 @Component 註解

package com.atguigu.mybatisplus.handler;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

3、測試

  • 測試新增
  • 測試修改

4、優化

  • 避免自動填充時開銷過大,填充前先判斷當前對象中是否有相關屬性
@Override
public void insertFill(MetaObject metaObject) {
    
    //其他程式碼
    
    //判斷是否具備author屬性
    boolean hasAuthor = metaObject.hasSetter("author");
    if(hasAuthor){
        log.info("start insert fill author....");
        this.strictInsertFill(metaObject, "author", String.class, "Helen"); 
    }
}
  • 用戶明確定義了屬性值,則無需自動填充,否則使用自動填充
@TableField(fill = FieldFill.INSERT)
private Integer age;
 @Override
public void insertFill(MetaObject metaObject) {
    //其他程式碼
    
    //判斷age是否賦值
    Object age = this.getFieldValByName("age", metaObject);
    if(age == null){
        log.info("start insert fill age....");
        this.strictInsertFill(metaObject, "age", String.class, "18");
    }
}

四、@TableLogic

1、邏輯刪除

  • 物理刪除:真實刪除,將對應數據從資料庫中刪除,之後查詢不到此條被刪除的數據
  • 邏輯刪除:假刪除,將對應數據中代表是否被刪除欄位的狀態修改為「被刪除狀態」,之後在資料庫中仍舊能看到此條數據記錄

使用場景:可以進行數據恢復

2、實現邏輯刪除

  • step1:資料庫中創建邏輯刪除狀態列

img

  • step2:實體類中添加邏輯刪除屬性
@TableLogic
@TableField(value = "is_deleted")
private Integer deleted;

3、測試

  • 測試刪除:刪除功能被轉變為更新功能
-- 實際執行的SQL
update user set is_deleted=1 where id = 1 and is_deleted=0
  • 測試查詢:被邏輯刪除的數據默認不會被查詢
-- 實際執行的SQL
select id,name,is_deleted from user where is_deleted=0