SpringBoot實現(xiàn)字段自動填充的兩種方式
creatby,updateby等字段自動填充
每個字段在插入數(shù)據(jù)庫,或者更新時都要在serviceimpl層對creatby,updateby等字段進行填充,這個太繁瑣了,以下兩種方法可以實現(xiàn)字段的自動填充。本項目使用第一種。
方法一:
首先創(chuàng)建一個AutoFillInterceptor類。下面會對代碼逐行分析。
以下代碼也可以直接復制粘貼,但前提是你的實體類中的自動填充的字段和下面四個靜態(tài)常量名字一樣。
@Component @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class AutoFillInterceptor implements Interceptor { private static final String CREATE_BY = "createdBy"; private static final String UPDATE_BY = "updatedBy"; private static final String CREATE_TIME = "createdAt"; private static final String UPDATE_TIME = "updatedAt"; @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; SqlCommandType sqlCommandType = ms.getSqlCommandType(); Object parameter = args[1]; if(parameter != null && sqlCommandType!=null){ if(SqlCommandType.INSERT.equals(sqlCommandType)){ if(parameter instanceof MapperMethod.ParamMap){ MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter; ArrayList list= (ArrayList) paramMap.get("list"); list.forEach(v->{ setFieldValByName(CREATE_TIME, LocalDateTime.now(), v); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v); }); paramMap.put("list", list); }else{ // 單條插入的情況 // 設置創(chuàng)建人和創(chuàng)建時間字段值 setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); } } else if(SqlCommandType.UPDATE.equals(sqlCommandType)){ // 更新操作 // 設置更新人和更新時間字段值 setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); } } // 繼續(xù)執(zhí)行原始方法 return invocation.proceed(); } private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) { MetaObject metaObject = SystemMetaObject.forObject(parameter); if (metaObject.hasSetter(fieldName)) { metaObject.setValue(fieldName, fieldVal); } } @Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } @Override public Object plugin(Object target) { return Interceptor.super.plugin(target); } }
代碼結(jié)構(gòu)與作用
這是一個實現(xiàn)了MyBatis攔截器(Interceptor
接口)的類AutoFillInterceptor
,用于在執(zhí)行SQL操作(INSERT或UPDATE)時,自動填充一些通用字段,比如創(chuàng)建時間(createdAt
)、更新時間(updatedAt
)等。
在企業(yè)級項目中,通常需要記錄數(shù)據(jù)的創(chuàng)建時間和修改時間,這個攔截器就是為了解決這種需求,在新增和修改數(shù)據(jù)時自動填充這些字段。下面我們來逐行分析代碼。
代碼逐行解析
@Component @Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) })
@Component
:Spring的注解,將這個類注冊為一個Spring Bean,便于管理。@Intercepts
:MyBatis的注解,聲明這是一個攔截器,并指定要攔截的目標。@Signature
:定義攔截器的具體攔截方法。type = Executor.class
:表示攔截MyBatis的Executor
類。method = "update"
:表示攔截update
方法,這個方法用于執(zhí)行更新操作(包括INSERT、UPDATE、DELETE)。args = {MappedStatement.class, Object.class}
:指定update
方法的參數(shù)類型,即SQL映射信息MappedStatement
和參數(shù)對象Object
。
public class AutoFillInterceptor implements Interceptor {
- 這幾行定義了一些常量,分別表示字段名稱,如創(chuàng)建者、修改者、創(chuàng)建時間和修改時間。這些常量將在攔截邏輯中用來自動填充字段。
@Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; SqlCommandType sqlCommandType = ms.getSqlCommandType(); Object parameter = args[1];
intercept
方法是攔截器的核心邏輯。Object[] args = invocation.getArgs()
:獲取攔截方法的參數(shù)。MappedStatement ms = (MappedStatement) args[0]
:獲取MappedStatement
,包含了有關(guān)SQL語句的信息。SqlCommandType sqlCommandType = ms.getSqlCommandType()
:獲取SQL的操作類型(INSERT、UPDATE、DELETE)。Object parameter = args[1]
:獲取參數(shù)對象,通常是用戶要插入或更新的數(shù)據(jù)。
if(parameter != null && sqlCommandType != null){
- 檢查參數(shù)是否為空,并確認操作類型是否非空,確保有必要繼續(xù)執(zhí)行后續(xù)操作。
if(SqlCommandType.INSERT.equals(sqlCommandType)){ if(parameter instanceof MapperMethod.ParamMap){ MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter; ArrayList list= (ArrayList) paramMap.get("list"); list.forEach(v -> { setFieldValByName(CREATE_TIME, LocalDateTime.now(), v); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v); }); paramMap.put("list", list); } else { // 單條插入的情況 // 設置創(chuàng)建人和創(chuàng)建時間字段值 setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter); setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); } }
if (SqlCommandType.INSERT.equals(sqlCommandType))
:如果當前SQL是INSERT操作:if (parameter instanceof MapperMethod.ParamMap)
:判斷參數(shù)是否是MapperMethod.ParamMap
類型,這通常用于批量插入。ArrayList list = (ArrayList) paramMap.get("list")
:從參數(shù)Map中獲取名為list
的參數(shù),這是批量插入的數(shù)據(jù)集合。list.forEach(v -> {...})
:對每個元素進行操作,調(diào)用setFieldValByName
方法設置createdAt
和updatedAt
為當前時間。
else
部分:處理單條插入的情況,直接給parameter
對象設置創(chuàng)建時間和更新時間。
else if(SqlCommandType.UPDATE.equals(sqlCommandType)){ // 更新操作 // 設置更新人和更新時間字段值 setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter); }
else if (SqlCommandType.UPDATE.equals(sqlCommandType))
:如果當前SQL是UPDATE操作:- 使用
setFieldValByName
方法將updatedAt
字段設置為當前時間。
- 使用
} // 繼續(xù)執(zhí)行原始方法 return invocation.proceed(); }
- 最終通過
invocation.proceed()
調(diào)用被攔截的方法,繼續(xù)執(zhí)行原始的數(shù)據(jù)庫操作。
private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) { MetaObject metaObject = SystemMetaObject.forObject(parameter); if (metaObject.hasSetter(fieldName)) { metaObject.setValue(fieldName, fieldVal); } }
setFieldValByName
方法用于設置對象中指定字段的值:MetaObject metaObject = SystemMetaObject.forObject(parameter)
:創(chuàng)建MetaObject
,用于操作傳入對象的元數(shù)據(jù)。if (metaObject.hasSetter(fieldName))
:檢查對象是否有對應字段的setter方法。metaObject.setValue(fieldName, fieldVal)
:如果有setter方法,則設置字段的值。
@Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } @Override public Object plugin(Object target) { return Interceptor.super.plugin(target); } }
setProperties
和plugin
方法是Interceptor
接口的默認實現(xiàn),plugin
方法用于生成代理對象。
總結(jié)
- 這個攔截器的作用是自動填充
createdAt
和updatedAt
字段,以便在執(zhí)行INSERT和UPDATE操作時自動記錄創(chuàng)建和更新時間。 - 主要攔截
Executor
的update
方法,通過判斷SQL類型來確定是INSERT還是UPDATE操作,從而設置相應字段。 - 使用了MyBatis的
MetaObject
工具類來動態(tài)操作參數(shù)對象的字段值。
通過這個攔截器,開發(fā)者不需要在業(yè)務代碼中手動設置createdAt
和updatedAt
,大大減少了重復代碼,也保證了這些公共字段的一致性和正確性。
方法二:
這個方法是使用自定義注解來寫的,所以要在需要填充的sql上加上這個注解。這個可能更加靈活更加簡單把。
公共字段自動填充
技術(shù)點:枚舉、注解、AOP、反射
創(chuàng)建時間、修改時間、創(chuàng)建人、修改人這4個公共字段。
為mapper方法加注解AutoFill,標識需要進行公共字段自動填充
自定義切面類AutoFillAspect,統(tǒng)一攔截加入了AutoFill注解的方法,通過反射為公共字段賦值。
在Mapper的方法上接入AutoFill注解。
public enum OperationType { 更新操作 UPDATE, 插入操作 INSERT } @Target(ElementType.METHOD)當前注解加在什么位置 @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { //數(shù)據(jù)庫操作類型:UPDATE INSERT OperationType value(); }
補充注解基本知識
public @interface MyAnnotation { // 定義注解的成員 String value(); // 這是一個名為"value"的成員 int count() default 1; // 這是一個名為"count"的成員,帶有默認值 } @MyAnnotation(value = "Hello", count = 3) public class MyClass { // 類的代碼 }
對于AutoFillAspect類切點、execution表達式
/** * 自定義切面,實現(xiàn)公共字段自動填充處理邏輯 */ @Aspect @Component @Slf4j public class AutoFillAspect { /** * 切入點 */ 所有的類,所有的方法,所有的參數(shù)類型 @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){} /** * 前置通知,在通知中進行公共字段的賦值 */ @Before("autoFillPointCut()")指定切入點 public void autoFill(JoinPoint joinPoint){連接點 log.info("開始進行公共字段自動填充..."); //獲取到當前被攔截的方法上的數(shù)據(jù)庫操作類型 MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法簽名對象 AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//獲得方法上的注解對象 OperationType operationType = autoFill.value();//獲得數(shù)據(jù)庫操作類型 //獲取到當前被攔截的方法的參數(shù)--實體對象 做一個約定,實體對象放第一個 Object[] args = joinPoint.getArgs(); if(args == null || args.length == 0){ return; } Object entity = args[0];實體 //準備賦值的數(shù)據(jù) LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); //根據(jù)當前不同的操作類型,為對應的屬性通過反射來賦值 if(operationType == OperationType.INSERT){ //為4個公共字段賦值 try { Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); //通過反射為對象屬性賦值 setCreateTime.invoke(entity,now); setCreateUser.invoke(entity,currentId); setUpdateTime.invoke(entity,now); setUpdateUser.invoke(entity,currentId); } catch (Exception e) { e.printStackTrace(); } }else if(operationType == OperationType.UPDATE){ //為2個公共字段賦值 try { Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); //通過反射為對象屬性賦值 setUpdateTime.invoke(entity,now); setUpdateUser.invoke(entity,currentId); } catch (Exception e) { e.printStackTrace(); } } } }
使用
@AutoFill(value = OperationType.UPDATE) void update(Employee employee);
自定義切面:實現(xiàn)公共字段的自動填充
這段代碼使用了 Spring AOP(面向切面編程)來實現(xiàn)對數(shù)據(jù)庫操作時,自動填充一些公共字段,例如創(chuàng)建時間、更新時間、創(chuàng)建人、更新人等。接下來,我們逐行解析這段代碼,以幫助你理解各個部分的功能和實現(xiàn)邏輯。
代碼結(jié)構(gòu)概覽
@Aspect @Component @Slf4j public class AutoFillAspect { // 切入點 @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){} // 前置通知 @Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint){ log.info("開始進行公共字段自動填充..."); ... } }
這段代碼定義了一個切面 AutoFillAspect
,它會在符合條件的數(shù)據(jù)庫操作方法執(zhí)行之前,通過前置通知 (@Before
) 自動對某些公共字段進行填充。
注解解釋
@Aspect
:表示當前類是一個切面類,用于定義通知和切入點。@Component
:把這個切面類注冊為 Spring 容器中的一個組件。@Slf4j
:用來啟用日志功能,以方便調(diào)試和記錄信息。
切入點定義
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){}
解釋:
@Pointcut
:用于定義一個切入點,描述哪些方法需要被切面邏輯攔截。execution(* com.sky.mapper.*.*(..))
:匹配com.sky.mapper
包下的所有類和所有方法,(..)
表示任意參數(shù)類型和數(shù)量。&& @annotation(com.sky.annotation.AutoFill)
:表示只攔截被@AutoFill
注解標記的方法。
通過這種定義,只有符合指定包下的類且有 @AutoFill
注解的方法,才會被切面邏輯攔截。
前置通知(Before Advice)
@Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint) { log.info("開始進行公共字段自動填充..."); ... }
- @Before("autoFillPointCut()"):這是前置通知,表示在切入點所匹配的方法執(zhí)行之前,執(zhí)行 autoFill() 方法。
- JoinPoint joinPoint:JoinPoint 是一個連接點,表示被攔截的方法,允許獲取到目標方法的一些信息,比如方法名和參數(shù)等。
獲取注解和方法信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); OperationType operationType = autoFill.value();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
:獲取當前攔截的方法的簽名信息,轉(zhuǎn)換為MethodSignature
類型。AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
:獲取方法上的@AutoFill
注解對象。OperationType operationType = autoFill.value();
:獲取注解中指定的數(shù)據(jù)庫操作類型(例如 INSERT 或 UPDATE)。
獲取方法參數(shù)
Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0) { return; } Object entity = args[0];
Object[] args = joinPoint.getArgs();
:獲取當前被攔截的方法的參數(shù)。if (args == null || args.length == 0)
:如果沒有參數(shù),直接返回。Object entity = args[0];
:假設第一個參數(shù)是實體對象,用于操作數(shù)據(jù)庫。這里有一個約定,即實體對象總是第一個參數(shù)。
準備賦值的數(shù)據(jù)
LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId();
LocalDateTime now = LocalDateTime.now();
:獲取當前時間,用于填充創(chuàng)建時間和更新時間。Long currentId = BaseContext.getCurrentId();
:獲取當前操作用戶的 ID,用于填充創(chuàng)建人和更新人信息。
根據(jù)操作類型進行賦值
插入操作(INSERT)
if (operationType == OperationType.INSERT) { try { Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class); Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class); Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setCreateTime.invoke(entity, now); setCreateUser.invoke(entity, currentId); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); } catch (Exception e) { e.printStackTrace(); } }
if (operationType == OperationType.INSERT)
:如果數(shù)據(jù)庫操作類型是插入(INSERT)。- 通過反射的方式獲取實體類中的
setCreateTime
、setCreateUser
、setUpdateTime
和setUpdateUser
方法。 invoke()
方法用于調(diào)用這些 setter 方法并傳入相應的值,完成公共字段的賦值。
更新操作(UPDATE)
else if (operationType == OperationType.UPDATE) { try { Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); setUpdateTime.invoke(entity, now); setUpdateUser.invoke(entity, currentId); } catch (Exception e) { e.printStackTrace(); } }
else if (operationType == OperationType.UPDATE)
:如果操作類型是更新(UPDATE)。- 這里只需填充更新相關(guān)的字段,即更新時間和更新人。
小結(jié)
這段代碼實現(xiàn)了對數(shù)據(jù)庫操作的公共字段自動填充,具體如下:
- 定義一個切面
AutoFillAspect
,用于攔截特定包中的方法,并且方法需要用@AutoFill
注解進行標記。 - 使用 AOP 的前置通知在方法執(zhí)行前進行字段自動填充。
- 通過反射機制獲取實體對象的方法并進行賦值,根據(jù)操作類型填充不同的字段。
這使得代碼變得更加簡潔和可維護,減少了重復的公共字段賦值邏輯,也方便對創(chuàng)建時間、更新時間等公共屬性的一致性管理。
以上就是SpringBoot實現(xiàn)字段自動填充的兩種方式的詳細內(nèi)容,更多關(guān)于SpringBoot字段自動填充的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java之jdbc連接mysql數(shù)據(jù)庫的方法步驟詳解
這篇文章主要介紹了Java之jdbc連接mysql數(shù)據(jù)庫的方法步驟詳解,JCBC技術(shù)是java開發(fā)必備的只是,jdbc連接mysql數(shù)據(jù)庫,這是一個比較簡單的方法,有興趣的可以了解一下2020-07-07關(guān)于MVC與SpringMVC的介紹、區(qū)別、執(zhí)行流程
這篇文章主要介紹了關(guān)于MVC與SpringMVC的介紹、區(qū)別、執(zhí)行流程,MVC框架的主要目標是將應用程序的業(yè)務邏輯(Model)與用戶界面(View)分離開來,從而提高應用程序的可維護性和可擴展性,需要的朋友可以參考下2023-05-05Mybatis下動態(tài)sql中##和$$的區(qū)別講解
今天小編就為大家分享一篇關(guān)于Mybatis下動態(tài)sql中##和$$的區(qū)別講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03SpringBoot實現(xiàn)kafka多源配置的示例代碼
實際開發(fā)中,不同的topic可能來自不同的集群,所以就需要配置不同的kafka數(shù)據(jù)源,基于springboot自動配置的思想,最終通過配置文件的配置,自動生成生產(chǎn)者及消費者的配置,本文介紹了SpringBoot實現(xiàn)kafka多源配置,需要的朋友可以參考下2024-06-06Java編程刪除鏈表中重復的節(jié)點問題解決思路及源碼分享
這篇文章主要介紹了Java編程刪除鏈表中重復的節(jié)點問題解決思路及源碼分享,具有一定參考價值,這里分享給大家,供需要的朋友了解。2017-10-10Java實現(xiàn)輕松處理日期和時間的API小結(jié)
這篇文章主要為大家詳細介紹了Java中的日期和時間API,可以輕松處理日期和時間,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-03-03Java利用Request請求如何獲取IP地址對應的省份、城市詳解
之前已經(jīng)給大家介紹了關(guān)于Java用Request請求獲取IP地址的相關(guān)內(nèi)容,那么下面這篇文章將給大家進入深入的介紹,關(guān)于Java利用Request請求如何獲取IP地址對應省份、城市的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10