当前位置: 首页 > news >正文

Springboot中基于注解实现公共字段自动填充

1.使用场景

 当我们有大量的表需要管理公共字段,并且希望提高开发效率和确保数据一致性时,使用这种自动填充方式是很有必要的。它可以达到一下作用
  • 统一管理数据库表中的公共字段:如创建时间、修改时间、创建人ID、修改人ID等,这些字段在所有表中都会存在,需要在插入和更新时保持一致性。

  • 避免重复代码:在不同的业务逻辑中频繁操作相同的字段(如每次插入记录时手动设置创建时间和创建人),使用自动填充可以避免手动填写这些字段,减少重复代码。

  • 提高开发效率:当项目中有大量类似的插入或更新操作时,通过自动填充机制可以减轻手动维护字段的负担,提高开发效率和代码的可维护性。

  • 确保数据的准确性:通过切面统一处理公共字段的填充,减少人为错误,确保创建时间、修改时间等字段始终准确。

2.步骤(以商城数据维护项目为例)

2.1  定义一个枚举类 OperationType

  这个类用于表示数据库操作的类型。枚举包含两个常量:

  1. UPDATE:表示更新操作。
  2. INSERT:表示插入操作。

枚举类通常用于标识某个操作是插入还是更新操作,方便在代码中进行逻辑判断或执行特定操作(如公共字段的自动填充),来让我在不同的数据库操作类型中统一处理。

package com.sky.enumeration;/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT
}

2.2 定义名为 AutoFill 的自定义注解

自定义注解用于标识某个方法需要进行公共字段的自动填充。注解包含一个刚才定义 OperationType 枚举值,用于指定该方法对应的数据库操作类型,是插入还是更新。

我下面对重要内容进行解释

  1. @Target(ElementType.METHOD):标注该注解只能应用在方法上。
  2. @Retention(RetentionPolicy.RUNTIME):指定该注解在运行时保留,可以通过反射机制获取该注解。
  3. OperationType value():这个方法定义了注解的一个属性,必须设置 OperationType 枚举值(INSERTUPDATE),来表示操作类型。
package com.sky.annotation;import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义注解,用于标识某个方法需要进行功能字段自动填充*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {// 数据库操作类型:UPDATE 或 INSERTOperationType value();
}

这个自定义注解的主要作用是在特定的数据库操作方法上使用,通过指定操作类型(插入或更新),结合其他逻辑实现公共字段的自动填充。

2.3 定义名为 AutoFillAspect 的切面类

这个类用于处理带有 @AutoFill 注解的方法,通过定义切面,切入点,前置通知实现

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;/*** 自定义切面,实现公共字段自动填充处理逻辑*/
@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("开始进行公共字段自动填充...");}    
}
  1. @Aspect:声明该类是一个切面类,负责切入业务逻辑并执行相应操作。
  2. @Component:将这个类注册为Spring的组件,以便在应用程序中自动管理和注入。
  3. @Slf4j:启用日志记录功能,方便在切面逻辑中记录日志(当前代码中没有具体使用日志)。
  4. @Pointcut:定义切入点,拦截我com.sky.mapper包下所有带有 @AutoFill 注解的方法。即拦截那些需要自动填充公共字段的数据库操作方法。
  5. @Before("autoFillPointCut()"):表示该方法会在切入点方法执行之前执行,这里切入点是那些符合 autoFillPointCut 定义的带有 @AutoFill 注解的方法。
  6. JoinPoint 参数JoinPoint 是AOP的一个接口,允许在切面中获取被拦截方法的相关信息(如方法名、参数等),便于进行逻辑处理。

那么当我们此时定义好切面后,对于我们自定义的这个auto fill这个注解,需要把它加到我们XXX Mapper的mybatis文件上面。

2.4  插入数据的Mapper方法(我这边以员工数据维护为例)

在我们特定数据库操作(Insert,Update)上使用了@AutoFill注解,指定了操作类型为OperationType.INSERT(UPDATE),意味着该方法会在插入数据或者更新数据时自动填充创建时间和创建人等公共字段。

//INSERT
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user) " +"values (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Employee employee);//UPDATE
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);

这边当调用 insert(Employee employee) 方法时,SQL插入语句将会执行,并且在插入之前,通过上面自定义 @AutoFill 注解的拦截逻辑自动为 create_timecreate_user 字段赋值。

2.5 切面类中autoFill方法逻辑完成

在上面2.3步骤中我们只是对下面方法进行Log输入,在这个步骤进一步完善通知中逻辑,下面的步骤都是在此方法中实现
    @Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){log.info("开始进行公共字段自动填充...");}   
2.5.1 需要获取 @AutoFill 注解的值,以判断当前操作是插入还是更新。
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType operationType = autoFill.value();
  1. MethodSignature signature = (MethodSignature) joinPoint.getSignature():通过 JoinPoint 获取方法签名。
  2. AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class):通过反射方法签名获取 @AutoFill 注解。
  3. OperationType operationType = autoFill.value():从注解中获取操作类型,判断是 INSERT 还是 UPDATE

2.5.2 获取实体对象

接下来获取当前方法的参数,因为实体对象是作为方法参数传入的。
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {return;
}
Object entity = args[0]; 
  1. Object[] args = joinPoint.getArgs():获取当前方法的参数列表。通常参数的第一个就是实体对象。
  2. Object entity = args[0]:假设实体对象是第一个参数,将其提取出来进行后续的字段赋值操作。

2.5.3 准备数据

为了填充公共字段,首先需要获取当前时间和当前用户ID。通常这些信息是从上下文环境或安全机制中获取的。
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
  1. LocalDateTime now = LocalDateTime.now():获取当前时间,用于设置 createTimeupdateTime
  2. Long currentId = BaseContext.getCurrentId():通过 BaseContext 获取当前操作的用户ID,用于设置 createUserupdateUser

2.5.3  根据操作类型自动填充字段

如果操作类型是 INSERT,那么需要为四个字段赋值:createTimecreateUserupdateTimeupdateUser
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) {throw new RuntimeException(e);}
}
  1. Method setCreateTime = entity.getClass().getDeclaredMethod(...):通过反射机制,获取实体对象的 setCreateTime 方法。
  2. setCreateTime.invoke(entity, now):通过反射调用 setCreateTime 方法,将当前时间 now 赋值给 createTime 字段。这里使用反射动态调用实体类中的 set 方法为指定字段赋值,从而实现灵活的字段处理。
  3. setCreateUsersetUpdateTimesetUpdateUser 也执行相同操作,分别赋值给实体的 createUserupdateTimeupdateUser 字段。
如果操作类型是 UPDATE,则只需要为 updateTimeupdateUser 两个字段赋值。
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) {throw new RuntimeException(e);}
}
  1. 通过反射获取 setUpdateTimesetUpdateUser 方法,并分别为 updateTimeupdateUser 字段赋值。
  2. 更新操作不需要设置 createTimecreateUser,因为这些字段只在插入时赋值。

总结:通过以上步骤实现公共字段填充,这样做其实是为了简化代码,减少重复操作,确保创建和修改相关字段在插入和更新操作中自动且一致地被赋值。

相关文章:

  • 9.23作业
  • 搭建rust开发环境
  • C语言第三周课
  • Threejs绘制圆锥体
  • 【STM32开发环境搭建】-4-在STM32CubeMX中新增Keil(MDK-ARM) 5的工程目录(包含指定路径的C和H文件)
  • 通信工程学习:什么是OFDM正交频分复用
  • LLaMa-Factory入门教程
  • 【Lunix】常用命令
  • 2024重生之回溯数据结构与算法系列学习(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
  • Android常用C++特性之std::unique_lock
  • 【Android】BottomSheet基本用法总结(BottomSheetDialog,BottomSheetDialogFragment)
  • TRIZ理论在机器人性能优化中的应用
  • 曲线图异常波形检测系统源码分享
  • Linux基础(三):安装CentOS7(系统安装+桥接联网+换源)
  • linux服务器安装原生的php环境
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • Effective Java 笔记(一)
  • JavaScript 一些 DOM 的知识点
  • Laravel核心解读--Facades
  • Mac转Windows的拯救指南
  • Vue2 SSR 的优化之旅
  • vuex 笔记整理
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 基于组件的设计工作流与界面抽象
  • 批量截取pdf文件
  • 使用权重正则化较少模型过拟合
  • 手写一个CommonJS打包工具(一)
  • 数据可视化之 Sankey 桑基图的实现
  • 云大使推广中的常见热门问题
  • 栈实现走出迷宫(C++)
  • 阿里云重庆大学大数据训练营落地分享
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • (1)svelte 教程:hello world
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (Matlab)遗传算法优化的BP神经网络实现回归预测
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (ZT)一个美国文科博士的YardLife
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (四)事件系统
  • (未解决)macOS matplotlib 中文是方框
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • .Net 6.0 处理跨域的方式
  • .net dataexcel 脚本公式 函数源码
  • .NET Remoting学习笔记(三)信道
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .net2005怎么读string形的xml,不是xml文件。
  • .NET6实现破解Modbus poll点表配置文件
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
  • .NET学习全景图
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48