MybatisPlus高級特性

MybatisPlus高級特性

1. 公共欄位自動填充

1.1 問題分析

在新增員工時需要設置創建時間、創建人、修改時間、修改人等欄位,在編輯員工時需要設置修改時間、修改人等欄位。這些欄位屬於公共欄位,也就是也就是在我們的系統中很多表中都會有這些欄位,如下:

image-20220809210533033

而針對於這些欄位,我們的賦值方式為:

A. 在新增數據時, 將createTime、updateTime 設置為當前時間, createUser、updateUser設置為當前登錄用戶ID。

B. 在更新數據時, 將updateTime 設置為當前時間, updateUser設置為當前登錄用戶ID。

1.2 基本功能實現

1.2.1 思路分析

Mybatis Plus公共欄位自動填充,也就是在插入或者更新的時候為指定欄位賦予指定的值,使用它的好處就是可以統一對這些欄位進行處理,避免了重複程式碼。在上述的問題分析中,我們提到有四個公共欄位,需要在新增/更新中進行賦值操作, 具體情況如下:

欄位名 賦值時機 說明
createTime 插入(INSERT) 當前時間
updateTime 插入(INSERT) , 更新(UPDATE) 當前時間
createUser 插入(INSERT) 當前登錄用戶ID
updateUser 插入(INSERT) , 更新(UPDATE) 當前登錄用戶ID

實現步驟:

1、在實體類的屬性上加入@TableField註解,指定自動填充的策略。

2、按照框架要求編寫元數據對象處理器,在此類中統一為公共欄位賦值,此類需要實現MetaObjectHandler介面

1.2.2 程式碼實現

1). 實體類的屬性上加入@TableField註解,指定自動填充的策略。

這裡就不提供程式碼,要注創建時間和創建人只在insert語句中需要自動填充。

  • FieldFill.INSERT: 插入時填充該屬性值
  • FieldFill.INSERT_UPDATE: 插入/更新時填充該屬性值

image-20220809210903131

2). 按照框架要求編寫元數據對象處理器,在此類中統一為公共欄位賦值,此類需要實現MetaObjectHandler介面。

放在項目的common包下

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

/**
 * 自定義元數據對象處理器
 */
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
    /**
     * 插入操作,自動填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共欄位自動填充[insert]...");
        log.info(metaObject.toString());
        
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("createUser",new Long(1));
        metaObject.setValue("updateUser",new Long(1));
    }

    /**
     * 更新操作,自動填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共欄位自動填充[update]...");
        log.info(metaObject.toString());

        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",new Long(1));
    }
}

1.3 功能完善

1.3.1 思路分析

前面我們已經完成了公共欄位自動填充功能的程式碼開發,但是還有一個問題沒有解決,就是我們在自動填充createUser和updateUser時設置的用戶id是固定值,現在我們需要完善,改造成動態獲取當前登錄用戶的id

大家可能想到,用戶登錄成功後我們將用戶id存入了HttpSession中,現在我從HttpSession中獲取不就行了?

image-20220809211236409

注意,我們在MyMetaObjectHandler類中是不能直接獲得HttpSession對象的,所以我們需要通過其他方式來獲取登錄用戶id。

  • 那麼我們先搞清楚一點,當我們在修改員工資訊時, 我們業務的執行流程是什麼樣子的,如下圖:

image-20220809211532195

1.3.2 ThreadLocal

ThreadLocal並不是一個Thread,而是Thread的局部變數。當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

ThreadLocal為每個執行緒提供單獨一份存儲空間,具有執行緒隔離的效果,只有在執行緒內才能獲取到對應的值,執行緒外則不能訪問當前執行緒對應的值。

ThreadLocal常用方法:

A. public void set(T value) : 設置當前執行緒的執行緒局部變數的值

B. public T get() : 返回當前執行緒所對應的執行緒局部變數的值

C. public void remove() : 刪除當前執行緒所對應的執行緒局部變數的值

我們可以在LoginCheckFilter(過濾器)的doFilter方法中獲取當前登錄用戶id,並調用ThreadLocal的set方法來設置當前執行緒的執行緒局部變數的值(用戶id),然後在MyMetaObjectHandler的updateFill方法中調用ThreadLocal的get方法來獲得當前執行緒所對應的執行緒局部變數的值(用戶id)。 如果在後續的操作中, 我們需要在Controller / Service中要使用當前登錄用戶的ID, 可以直接從ThreadLocal直接獲取。

1.3.3 操作步驟

實現步驟:

1). 編寫UserThreadLocal工具類,基於ThreadLocal封裝的工具類

2). 在LoginCheckFilter的doFilter方法中調用BaseContext來設置當前登錄用戶的id

3). 在MyMetaObjectHandler的方法中調用BaseContext獲取登錄用戶的id

1.3.4 程式碼實現

1). UserThreadLocal工具類

/**
 * 基於ThreadLocal封裝工具類,用戶保存和獲取當前登錄用戶id
 */
public class UserThreadLocal {

    private UserThreadLocal() {
    }

    private static ThreadLocal<Long> THREADLOCAL = new ThreadLocal<>();

    /**
     * 設置值
     *
     * @param id
     */
    public static void setCurrentId(Long id) {
        THREADLOCAL.set(id);
    }

    /**
     * 獲取值
     *
     * @return
     */
    public static Long getCurrentId() {
        return THREADLOCAL.get();
    }

    public static void remove() {
        THREADLOCAL.remove();
    }
}

2).LoginCheckFilter中存放當前登錄用戶到ThreadLocal

在doFilter方法中, 判定用戶是否登錄, 如果用戶登錄, 在放行之前, 獲取HttpSession中的登錄用戶資訊, 調用BaseContext的setCurrentId方法將當前登錄用戶ID存入ThreadLocal。

  • 有些小夥伴肯定會有疑問,清除id的方法就寫在下面,這不就等於沒設嗎,方法都沒走完就給清除了。

  • filterChain.doFilter(request, response); 會等待 Controller,等一系類方法的調用,才會結束

  • 我解釋的不是很完美,大家可以自行測試

image-20220809212239099

3). MyMetaObjectHandler中從ThreadLocal中獲取

將之前在程式碼中固定的當前登錄用戶1, 修改為動態調用UserThreadLocal中的getCurrentId方法獲取當前登錄用戶ID

image-20220809212827689

image-20220809212843687

1.3.5 功能測試

完善了元數據對象處理器之後,我們就可以重新啟動項目,對插入或者更新的介面去測試。

2. 邏輯刪除

2.1 問題分析

在實際的項目中,數據是十分寶貴的,所以不會做到真正的去刪除。資料庫中一般會存在如下欄位:

image-20220809213336542

2.2 思路分析

MybatisPlus為我們提供了許多種的配置方法。

  • 可以在yaml中配置全局的邏輯刪除
  • 也可以在每個實體類中

2.3 程式碼實現

2.3.1配置全局

配置yaml

  • 圖中紅框中的就是全局邏輯刪除的配置,其他的可以根據需要自行添加
  • logic-delete-field: deleted 配置的一定對應上實體類的變數名稱
  • @TableField(value = "is_deleted") value對應的是資料庫的欄位

image-20220809214119956

mybatis-plus:
  configuration:
    #在映射實體或者屬性時,將資料庫中表名和欄位名中的下劃線去掉,按照駝峰命名法映射 address_book ---> AddressBook
    map-underscore-to-camel-case: true
    #日誌輸出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID # 全局配置資料庫id生成的策略
      logic-delete-field: deleted # 全局邏輯刪除的實體欄位名(實體類)
      logic-delete-value: 1 # 邏輯已刪除值(默認為1)
      logic-not-delete-value: 0 # 邏輯未刪除值(默認為0)

image-20220809213830379

2.3.2實體類中

  • @TableLogic(value = "0", delval = "1")配置邏輯刪除欄位的值,value的值表示未刪除的時候的值,delval的值表示已刪除時候的值;

image-20220809214409671