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);
}
}
代碼結構與作用
這是一個實現(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,包含了有關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方法用于生成代理對象。
總結
- 這個攔截器的作用是自動填充
createdAt和updatedAt字段,以便在執(zhí)行INSERT和UPDATE操作時自動記錄創(chuàng)建和更新時間。 - 主要攔截
Executor的update方法,通過判斷SQL類型來確定是INSERT還是UPDATE操作,從而設置相應字段。 - 使用了MyBatis的
MetaObject工具類來動態(tài)操作參數(shù)對象的字段值。
通過這個攔截器,開發(fā)者不需要在業(yè)務代碼中手動設置createdAt和updatedAt,大大減少了重復代碼,也保證了這些公共字段的一致性和正確性。
方法二:
這個方法是使用自定義注解來寫的,所以要在需要填充的sql上加上這個注解。這個可能更加靈活更加簡單把。
公共字段自動填充
技術點:枚舉、注解、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)邏輯。
代碼結構概覽
@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();:獲取當前攔截的方法的簽名信息,轉換為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)。- 這里只需填充更新相關的字段,即更新時間和更新人。
小結
這段代碼實現(xiàn)了對數(shù)據(jù)庫操作的公共字段自動填充,具體如下:
- 定義一個切面
AutoFillAspect,用于攔截特定包中的方法,并且方法需要用@AutoFill注解進行標記。 - 使用 AOP 的前置通知在方法執(zhí)行前進行字段自動填充。
- 通過反射機制獲取實體對象的方法并進行賦值,根據(jù)操作類型填充不同的字段。
這使得代碼變得更加簡潔和可維護,減少了重復的公共字段賦值邏輯,也方便對創(chuàng)建時間、更新時間等公共屬性的一致性管理。
以上就是SpringBoot實現(xiàn)字段自動填充的兩種方式的詳細內(nèi)容,更多關于SpringBoot字段自動填充的資料請關注腳本之家其它相關文章!
相關文章
出現(xiàn)java.lang.UnsupportedClassVersionError錯誤的原因以及解決方法
這篇文章主要給大家介紹了關于出現(xiàn)java.lang.UnsupportedClassVersionError錯誤的原因以及解決方法,文中通過圖文以及代碼示例將這個錯誤介紹的非常詳細,需要的朋友可以參考下2024-05-05

