Springboot公共字段填充及ThreadLocal模塊改進(jìn)方案
1. 公共字段自動(dòng)填充
1.1 問題分析
在新增員工時(shí)需要設(shè)置創(chuàng)建時(shí)間、創(chuàng)建人、修改時(shí)間、修改人等字段,在編輯員工時(shí)需要設(shè)置修改時(shí)間、修改人等字段。這些字段屬于公共字段,也就是也就是在系統(tǒng)中很多表中都會(huì)有這些字段,如下:
而針對(duì)于這些字段的賦值方式為:
A. 在新增數(shù)據(jù)時(shí), 將createTime、updateTime 設(shè)置為當(dāng)前時(shí)間, createUser、updateUser設(shè)置為當(dāng)前登錄用戶ID。
B. 在更新數(shù)據(jù)時(shí), 將updateTime 設(shè)置為當(dāng)前時(shí)間, updateUser設(shè)置為當(dāng)前登錄用戶ID。
目前,在項(xiàng)目中處理這些字段都是在每一個(gè)業(yè)務(wù)方法中進(jìn)行賦值操作,如下
如果都按照上述的操作方式來處理這些公共字段, 需要在每一個(gè)業(yè)務(wù)方法中進(jìn)行操作, 編碼相對(duì)冗余、繁瑣。
改進(jìn):使用Mybatis Plus提供的公共字段自動(dòng)填充功能來簡(jiǎn)化開發(fā)。
1.2 基本功能實(shí)現(xiàn)
1.2.1 思路分析
Mybatis Plus公共字段自動(dòng)填充,即在插入或者更新的時(shí)候?yàn)橹付ㄗ侄钨x予指定的值,好處統(tǒng)一對(duì)這些字段進(jìn)行處理,避免了重復(fù)代碼。在上述的問題分析中,有四個(gè)公共字段需要在新增/更新中進(jìn)行賦值操作, 具體情況如下:
字段名 | 賦值時(shí)機(jī) | 說明 |
---|---|---|
createTime | 插入(INSERT) | 當(dāng)前時(shí)間 |
updateTime | 插入(INSERT) , 更新(UPDATE) | 當(dāng)前時(shí)間 |
createUser | 插入(INSERT) | 當(dāng)前登錄用戶ID |
updateUser | 插入(INSERT) , 更新(UPDATE) |
實(shí)現(xiàn)步驟:
1、在實(shí)體類的屬性上加入@TableField注解,指定自動(dòng)填充的策略。
2、按照框架要求編寫元數(shù)據(jù)對(duì)象處理器,在此類中統(tǒng)一為公共字段賦值,此類需要實(shí)現(xiàn)MetaObjectHandler接口。
1.3 代碼實(shí)現(xiàn)
1). 實(shí)體類的屬性上加入@TableField注解,指定自動(dòng)填充的策略。
在員工Employee實(shí)體類的公共字段屬性上, 加上注解, 指定填充策略。
package com.itheima.reggie.entity; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /**@Description: 員工實(shí)體類 該實(shí)體類主要用于和員工表 employee 進(jìn)行映射。 * @version v1.0 * @author LiBiGo * @date 2022/8/12 11:05 */ // 在實(shí)體類上添加@Data注解,可以省去代碼中大量的 get()、 set()、 toString() 等方法,提高代碼的簡(jiǎn)潔: @Data public class Employee implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String username; private String name; private String password; private String phone; private String sex; // map-underscore-to-camel-case: true // 在映射實(shí)體或者屬性時(shí),將數(shù)據(jù)庫(kù)中表名和字段名中的下劃線去掉,按照駝峰命名法映射 private String idNumber; //身份證 因?yàn)樵谂渲梦募性O(shè)置駝峰命名,所以與數(shù)據(jù)庫(kù)中的不太一樣,數(shù)據(jù)庫(kù)中為id_number private Integer status; @TableField(fill = FieldFill.INSERT) // 插入時(shí)填充該屬性值 private LocalDateTime createTime; // 同上 駝峰命名法 @TableField(fill = FieldFill.INSERT_UPDATE) //插入、更新時(shí)填充該屬性值 private LocalDateTime updateTime; // 同上 駝峰命名法 @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) //插入和更新時(shí)填充 private Long updateUser; }
2). 按照框架要求編寫元數(shù)據(jù)對(duì)象處理器,在此類中統(tǒng)一為公共字段賦值,此類需要實(shí)現(xiàn)MetaObjectHandler接口。
package com.itheima.reggie.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; /** * Description: 自定義元數(shù)據(jù)對(duì)象處理器|統(tǒng)一為公共字段賦值,此類需要實(shí)現(xiàn)MetaObjectHandler接口。 * @author w * @version 1.0 * @date 2022/8/15 12:05 */ @Component @Slf4j public class MyMetaObjecthandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { /**@Description: 插入操作自動(dòng)填充 * @version v1.0 * @author LiBiGo * @date 2022/8/15 12:14 */ log.info("公共字段自動(dòng)填充insert..."); log.info(metaObject.toString()); metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("createUser", BaseContext.getCurrentId()); // 使用了Thread 看后續(xù) metaObject.setValue("updateUser", BaseContext.getCurrentId()); } @Override public void updateFill(MetaObject metaObject) { /**@Description: 更新操作自動(dòng)填充 * @version v1.0 * @author LiBiGo * @date 2022/8/15 12:14 */ log.info("公共字段自動(dòng)填充update..."); log.info(metaObject.toString()); long id = Thread.currentThread().getId(); log.info("線程ID:{}",id); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("updateUser", BaseContext.getCurrentId()); } }
2 使用ThreadLocal對(duì)公共字段填充功能進(jìn)行完善
2.1 思路分析
2.1.1 提出設(shè)想
前面已經(jīng)完成公共字段自動(dòng)填充功能的代碼開發(fā),但是還有一個(gè)問題沒有解決,就是在自動(dòng)填充createUser和updateUser時(shí)設(shè)置的用戶id是固定值,現(xiàn)在需要改造成動(dòng)態(tài)獲取當(dāng)前登錄用戶的id。
提出設(shè)想:用戶登錄成功后將用戶id存入了HttpSession中,現(xiàn)在從HttpSession中獲取不就行了?
存在問題:在MyMetaObjectHandler類中是不能直接獲得HttpSession對(duì)象的,所以需要通過其他方式來獲取登錄用戶id。
2.1.2 分析問題
在修改員工信息時(shí), 業(yè)務(wù)的執(zhí)行流程如下圖:
客戶端發(fā)送的每次http請(qǐng)求,對(duì)應(yīng)的在服務(wù)端都會(huì)分配一個(gè)新的線程來處理,在處理過程中涉及到下面類中的方法都屬于相同的一個(gè)線程:
1). LoginCheckFilter的doFilter方法
2). EmployeeController的update方法
3). MyMetaObjectHandler的updateFill方法
我們可以在上述類的方法中加入如下代碼(獲取當(dāng)前線程ID,并輸出):
long id = Thread.currentThread().getId(); log.info("線程id為:{}",id);
執(zhí)行編輯員工功能進(jìn)行驗(yàn)證,通過觀察控制臺(tái)輸出可以發(fā)現(xiàn),一次請(qǐng)求對(duì)應(yīng)的線程id是相同的:
經(jīng)過上述的分析之后,發(fā)現(xiàn)可以使用JDK提供的一個(gè)ThreadLocal類來解決此問題。
2.2 ThreadLocal
2.2.1 ThreadLocal基本概述
ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量。
當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。
ThreadLocal為每個(gè)線程提供單獨(dú)一份存儲(chǔ)空間,具有線程隔離的效果,只有在線程內(nèi)才能獲取到對(duì)應(yīng)的值,線程外則不能訪問當(dāng)前線程對(duì)應(yīng)的值。
2.2.2 ThreadLocal常用方法
A. public void set(T value) : 設(shè)置當(dāng)前線程的線程局部變量的值
B. public T get() : 返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值
C. public void remove() : 刪除當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值
我們可以在LoginCheckFilter的doFilter方法中獲取當(dāng)前登錄用戶id,并調(diào)用ThreadLocal的set方法來設(shè)置當(dāng)前線程的線程局部變量的值(用戶id),然后在MyMetaObjectHandler的updateFill方法中調(diào)用ThreadLocal的get方法來獲得當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值(用戶id)。 如果在后續(xù)的操作中, 我們需要在Controller / Service中要使用當(dāng)前登錄用戶的ID, 可以直接從ThreadLocal直接獲取。
2.3 操作步驟
1). 編寫B(tài)aseContext工具類,基于ThreadLocal封裝的工具類
2). 在LoginCheckFilter的doFilter方法中調(diào)用BaseContext來設(shè)置當(dāng)前登錄用戶的id
3). 在MyMetaObjectHandler的方法中調(diào)用BaseContext獲取登錄用戶的id
2.4 代碼實(shí)現(xiàn)
1). BaseContext工具類
package com.itheima.reggie.common; /** * Description: 基于ThreadLocal封裝工具類,用戶保存和獲取當(dāng)前登陸用戶ID * @date 2022/8/15 12:40 */ public class BaseContext { private static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); /** * 設(shè)置值 * @param id */ public static void setCurrentId(Long id){ threadLocal.set(id); } /** * 獲取值 * @return */ public static Long getCurrentId(){ return threadLocal.get(); } }
2).LoginCheckFilter中存放當(dāng)前登錄用戶到ThreadLocal
在doFilter方法中, 判定用戶是否登錄, 如果用戶登錄, 在放行之前, 獲取HttpSession中的登錄用戶信息, 調(diào)用BaseContext的setCurrentId方法將當(dāng)前登錄用戶ID存入ThreadLocal。
package com.itheima.reggie.filter; import com.alibaba.fastjson.JSON; import com.itheima.reggie.common.BaseContext; import com.itheima.reggie.common.R; import lombok.extern.slf4j.Slf4j; import org.springframework.util.AntPathMatcher; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Description: 檢查用戶是否已經(jīng)完成登陸 */ @WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*") //設(shè)置攔截器,設(shè)置攔截的網(wǎng)頁區(qū)域 @Slf4j public class LoginCheckFilter implements Filter { // 路徑匹配器,支持通配符,因?yàn)橄旅娴男蛄惺褂昧送ㄅ浞? // AntPathMatcher匹配規(guī)則 ? 匹配一個(gè)字符 * 匹配0個(gè)或多個(gè)字符 ** 匹配0個(gè)或多個(gè)目錄/字符 public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; // A. 獲取本次請(qǐng)求的URI String requestURI = request.getRequestURI(); log.info("攔截的請(qǐng)求:{}",requestURI); // 定義不攔截的序列,只攔截頁面數(shù)據(jù)請(qǐng)求,不攔截頁面格式 String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**" }; // B. 判斷本次請(qǐng)求, 是否需要登錄, 才可以訪問 boolean check = check(urls, requestURI); // C. 如果不需要,則直接放行 if(check){ log.info("本次請(qǐng)求{}不需要處理",requestURI); filterChain.doFilter(request,response); return; } // D. 判斷登錄狀態(tài),如果已登錄,則直接放行 if(request.getSession().getAttribute("employee")!=null){ log.info("用戶已登錄,用戶id為:{}",request.getSession().getAttribute("employee")); Long empId = (Long) request.getSession().getAttribute("employee"); // 獲取當(dāng)前用戶登陸id BaseContext.setCurrentId(empId); //設(shè)置線程共享 filterChain.doFilter(request,response); // 補(bǔ)課 不清楚???????????? long id = Thread.currentThread().getId(); log.info("檢測(cè)是否登陸線程ID:{}",id); return; } // E. 如果未登錄, 則返回未登錄結(jié)果 response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); return; } public boolean check(String[] urls,String requestURI){ /**@Description: 路徑匹配,檢查本次請(qǐng)求是否需要放行 * @author LiBiGo * @date 2022/8/12 16:50 */ for (String url : urls) { boolean match = PATH_MATCHER.match(url, requestURI); // 使用通配符對(duì)象,配對(duì)資源 if(match){ return true; } } return false; } }
3). MyMetaObjectHandler中從ThreadLocal中獲取
將之前在代碼中添加動(dòng)態(tài)調(diào)用BaseContext中的getCurrentId方法獲取當(dāng)前登錄用戶ID
3 功能測(cè)試
編寫完了元數(shù)據(jù)對(duì)象處理器之后,我們就可以將之前在新增和修改方法中手動(dòng)賦值的代碼刪除或注釋掉。
完善了元數(shù)據(jù)對(duì)象處理器重新啟動(dòng)項(xiàng)目,完成登錄操作后,在員工管理模塊中,測(cè)試增加/更新員工信息功能,直接查詢數(shù)據(jù)庫(kù)數(shù)據(jù)變更,看看我們?cè)谛略?修改數(shù)據(jù)時(shí),這些公共字段數(shù)據(jù)是否能夠完成自動(dòng)填充, 并且看看填充的create_user 及 update_user字段值是不是本地登錄用戶的ID。
以上就是Springboot公共字段填充及ThreadLocal模塊改進(jìn)方案的詳細(xì)內(nèi)容,更多關(guān)于Springboot 公共字段填充的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- springboot在filter中如何用threadlocal存放用戶身份信息
- SpringBoot中的ThreadLocal保存請(qǐng)求用戶信息的實(shí)例demo
- springboot登錄攔截器+ThreadLocal實(shí)現(xiàn)用戶信息存儲(chǔ)的實(shí)例代碼
- SpringBoot ThreadLocal 簡(jiǎn)單介紹及使用詳解
- SpringBoot+ThreadLocal+AbstractRoutingDataSource實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
- SpringBoot ThreadLocal實(shí)現(xiàn)公共字段自動(dòng)填充案例講解
- SpringBoot通過ThreadLocal實(shí)現(xiàn)登錄攔截詳解流程
- springboot 使用ThreadLocal的實(shí)例代碼
- SpringBoot中使用?ThreadLocal?進(jìn)行多線程上下文管理及注意事項(xiàng)小結(jié)
相關(guān)文章
通俗易懂學(xué)習(xí)java并發(fā)工具類-Semaphore,Exchanger
這篇文章主要介紹了java并發(fā)工具類-Semaphore,Exchanger,java并發(fā)工具類有很多,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面小編帶大家來一起學(xué)習(xí)一下吧2019-06-06Spring Boot參數(shù)校驗(yàn)及分組校驗(yàn)的使用教程
在日常的開發(fā)中,參數(shù)校驗(yàn)是非常重要的一個(gè)環(huán)節(jié),嚴(yán)格參數(shù)校驗(yàn)會(huì)減少很多出bug的概率,增加接口的安全性,下面這篇文章主要給大家介紹了關(guān)于Spring Boot參數(shù)校驗(yàn)及分組校驗(yàn)使用的相關(guān)資料,需要的朋友可以參考下2021-08-08Java異常處理Guava?Throwables類使用實(shí)例解析
這篇文章主要為大家介紹了Java異常處理神器Guava?Throwables類使用深入詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Java面試題沖刺第四天--數(shù)據(jù)庫(kù)
這篇文章主要為大家分享了最有價(jià)值的三道數(shù)據(jù)庫(kù)面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下2021-07-07基于SpringCloud手寫一個(gè)簡(jiǎn)易版Sentinel
SpringCloud Alibaba Sentinel是當(dāng)前最為流行一種熔斷降級(jí)框架,簡(jiǎn)單易用的方式可以快速幫助我們實(shí)現(xiàn)服務(wù)的限流和降級(jí),保證服務(wù)的穩(wěn)定性。2021-05-05Spring Boot前后端分離開發(fā)模式中的跨域問題及解決方法
本文介紹了解決Spring Boot前端Vue跨域問題的實(shí)戰(zhàn)經(jīng)驗(yàn),并提供了后端和前端的配置示例,通過配置后端和前端,我們可以輕松解決跨域問題,實(shí)現(xiàn)正常的前后端交互,需要的朋友可以參考下2023-09-09MyBatis Mapper.xml入?yún)ist使用in函數(shù)問題
文章主要講述了在使用MyBatis的Mapper.xml文件時(shí),如何正確地在in函數(shù)中使用List作為入?yún)?作者強(qiáng)調(diào)了完整拷貝<if>...</if>格式的重要性,并指出稍微的改動(dòng)就會(huì)導(dǎo)致錯(cuò)誤2025-02-02