SpringBoot ThreadLocal實現(xiàn)公共字段自動填充案例講解
一.字段自動填充引入
先看一個現(xiàn)象,在之前寫好的表中,我們發(fā)現(xiàn)有很多字段重復(fù)出現(xiàn)

比如update_time、create_time、create_user…
這就導(dǎo)致需要在Controller層中每一次對表中數(shù)據(jù)進行修改后調(diào)用一次.setCreateTime(LocalDateTime.now());或者setUpdateTime(LocalDateTime.now());等等“硬編碼問題又出現(xiàn)了”顯得格外麻煩
這些共性字段如何統(tǒng)一拿出來處理呢?MyBatisPlus給了我們解決方案,為了實現(xiàn)這一功能:
首先
我們需要在公共字段對應(yīng)的實體屬性上加上@TableField注解與指定填充策略,就像這樣:
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
不難看出,fill的類型是一個枚舉類

若是不需要處理則選擇DEAFULT,若是增添后需要修改字段的值選擇INSERT,若是涉及修改數(shù)據(jù)時需要修改字段的值則選擇UPDATE,若是插入和修改都需要設(shè)置字段的值則選擇INSERT_UPDATE就像updateTime和updateUser,只要涉及對數(shù)據(jù)的操作就要修改字段的值
類似于全局異常處理器,為了實現(xiàn)字段填充也需要定義一個元數(shù)據(jù)對象處理器
二.元數(shù)據(jù)對象處理器
在定義的類中實現(xiàn)MetaObjecthandler接口,并重寫接口中策略對應(yīng)的方法,就像這樣:
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自動填充...");
log.info(metaObject.toString());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自動填充...");
log.info(metaObject.toString());
}
}這里有點像繼承HttpServlet那個套路,重寫doGet()與doPost()方法,要實現(xiàn)什么功能就寫在對應(yīng)的方法里…
重寫完方法,不妨debug一下:

發(fā)現(xiàn)我實現(xiàn)接口后在方法里拿到了實體的數(shù)據(jù),并封裝到了形參里的metaObject對象中,接下來我就可以利用此對象來做公共字段的集中處理了!
所謂的自動填充也正是因為該類擁有@Component注解,在每一次的項目啟動后就會被掃描到,加載到,而類中的方法又實現(xiàn)了功能,所以才可以做到自動填充字段!
回到正題
我們要做的是把公共的字段做到統(tǒng)一填充,具體實現(xiàn)則是在重寫的方法里調(diào)用setValue()方法并傳進去字段與參數(shù),就像這樣:
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自動填充...");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自動填充...");
log.info(metaObject.toString());
metaObject.setValue("updateTime",LocalDateTime.now());
}相比之前controller層中的employee.setCreateTime(LocalDateTime.now());/employee.setUpdateTime(LocalDateTime.now());來看還是比較好理解,無非就是“茴“字的另一種寫法
(但卻實現(xiàn)了一勞永逸)…
可能有人會問,懶羊羊你的updateUser與createUser字段怎么不處理呢?
確實,還記得之前在controller層中我們是怎么處理的嗎?

為了確定User是誰,我們從Session里拿到了操作的對象id,并調(diào)用set方法修改了對象的字段值
按照上面的經(jīng)驗,為了能夠動態(tài)的拿到id,我們應(yīng)該這樣設(shè)置:

可是,在此方法中metaObject對象好像不能利用Session里的empID,那要如何獲得對象的標(biāo)識呢?
不就是一個標(biāo)識么,我用線程id可以嗎?
三.Threadlocal的使用
在這之前,需要明白一點,每當(dāng)客戶端發(fā)送一次HTTP請求,對應(yīng)在服務(wù)器端會分配一個新的線程來處理。項目設(shè)計到現(xiàn)在為止,以點擊一次保存按鈕作為一次請求,它會觸發(fā)過濾器、調(diào)用Controller層、MetaObjectHandler層的方法
我們通過Thread的內(nèi)部方法:long id = Thread.currentThread().getId();來獲得當(dāng)前線程的id,以日志的形式log.info("當(dāng)前線程id:{}",id);輸出到控制臺:

可見,三者的線程id相同,說明他們在同一個線程中,這就保證了一致性,也正是因為這個特性,所以可用來當(dāng)作表中的字段id使用來作為標(biāo)識

在此基礎(chǔ)上,大致方向已經(jīng)敲定是利用線程的特性,具體要如何實現(xiàn)呢?那就不得不需要了解一下ThreadLocal這個概念了:
1.ThreadLocal并不是一個Thread,而是Thread的局部變量。
2.當(dāng)使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
3.ThreadLocal為每個線程提供單獨一份存儲空間,具有線程隔離的效果,只有在線程內(nèi)才能獲取到對應(yīng)的值,線程外則不能訪問
常用方法
| public void set(T value) | 設(shè)置當(dāng)前線程局部變量的值 |
|---|---|
| public T get() | 返回當(dāng)前線程所對應(yīng)的線程局部變量的值 |
了解到ThreadLocal特性,我們就可以結(jié)合“同一線程”這一特性來獲得那個對象唯一的SessionId。

?? 在前面的登錄功能中我們設(shè)置了一個filter,在filter中我們已經(jīng)拿到過了一次SessionId,寫到這里解決方案不就出來了嘛——把filter中的SessionId當(dāng)作參數(shù)傳給ThreadLocal的set方法,然后在MetaObjectHandler實現(xiàn)類(元數(shù)據(jù)對象處理器)中調(diào)用ThreadLocal的get()方法得到SessionId
就像這樣:
1.為了規(guī)范,我們封裝一個類,功能是調(diào)用ThreadLocal的方法
/**
* 基于ThreadLocal封裝工具類,用于保護和獲取當(dāng)前登錄id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal=new ThreadLocal();
public static void setCurrentId(Long currentId){
threadLocal.set(currentId);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
2.在filter中做到SessionId的遷移

3.在MetaObjectHandler實現(xiàn)類中利用SessionId完成公共字段填充設(shè)置
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自動填充...");
log.info(metaObject.toString());
long id = Thread.currentThread().getId();
log.info("當(dāng)前線程id:{}",id);
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
這樣,就可以一勞永逸咯~
又是一個小技巧?。?/p>
到此這篇關(guān)于SpringBoot ThreadLocal實現(xiàn)公共字段自動填充案例講解的文章就介紹到這了,更多相關(guān)SpringBoot自動填充內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java自帶定時任務(wù)ScheduledThreadPoolExecutor實現(xiàn)定時器和延時加載功能
今天小編就為大家分享一篇關(guān)于Java自帶定時任務(wù)ScheduledThreadPoolExecutor實現(xiàn)定時器和延時加載功能,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
spring學(xué)習(xí)之util:properties的使用
這篇文章主要介紹了spring學(xué)習(xí)之util:properties的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
Java8 自定義CompletableFuture的原理解析
這篇文章主要介紹了Java8 自定義CompletableFuture的原理解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
SpringMVC源碼之HandlerMapping處理器映射器解析
這篇文章主要介紹了SpringMVC源碼之HandlerMapping處理器映射器解析,在Spring?MVC中,HandlerMapping處理器映射器用于確定請求處理器對象,請求處理器可以是任何對象,只要它們使用了@Controller注解或注解@RequestMapping,需要的朋友可以參考下2023-08-08

