SpringBoot实现字段自动填充的两种方式

 更新时间:2024年11月11日 11:21:49   作者:waterme1onY  
每个字段在插入数据库,或者更新时都要在serviceimpl层对creatby,updateby等字段进行填充,这个太繁琐了,所以本文给大家介绍了SpringBoot实现字段自动填充的两种方式,需要的朋友可以参考下

creatby,updateby等字段自动填充

每个字段在插入数据库,或者更新时都要在serviceimpl层对creatby,updateby等字段进行填充,这个太繁琐了,以下两种方法可以实现字段的自动填充。本项目使用第一种。

方法一:

首先创建一个AutoFillInterceptor类。下面会对代码逐行分析。
以下代码也可以直接复制粘贴,但前提是你的实体类中的自动填充的字段和下面四个静态常量名字一样。

@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{
                    // 单条插入的情况
                    // 设置创建人和创建时间字段值
                    setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);
                    setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
                }
            }
            else if(SqlCommandType.UPDATE.equals(sqlCommandType)){
                // 更新操作
                // 设置更新人和更新时间字段值
                setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
            }
        }

        // 继续执行原始方法
        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);
    }
}

代码结构与作用

这是一个实现了MyBatis拦截器(Interceptor接口)的类AutoFillInterceptor,用于在执行SQL操作(INSERT或UPDATE)时,自动填充一些通用字段,比如创建时间(createdAt)、更新时间(updatedAt)等。

在企业级项目中,通常需要记录数据的创建时间和修改时间,这个拦截器就是为了解决这种需求,在新增和修改数据时自动填充这些字段。下面我们来逐行分析代码。

代码逐行解析

@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方法,这个方法用于执行更新操作(包括INSERT、UPDATE、DELETE)。
      • args = {MappedStatement.class, Object.class}:指定update方法的参数类型,即SQL映射信息MappedStatement和参数对象Object
public class AutoFillInterceptor implements Interceptor {
  • 这几行定义了一些常量,分别表示字段名称,如创建者、修改者、创建时间和修改时间。这些常量将在拦截逻辑中用来自动填充字段。
    @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():获取拦截方法的参数。
    • MappedStatement ms = (MappedStatement) args[0]:获取MappedStatement,包含了有关SQL语句的信息。
    • SqlCommandType sqlCommandType = ms.getSqlCommandType():获取SQL的操作类型(INSERT、UPDATE、DELETE)。
    • 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 {
                    // 单条插入的情况
                    // 设置创建人和创建时间字段值
                    setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);
                    setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
                }
            }
  • if (SqlCommandType.INSERT.equals(sqlCommandType)):如果当前SQL是INSERT操作:
    • if (parameter instanceof MapperMethod.ParamMap):判断参数是否是MapperMethod.ParamMap类型,这通常用于批量插入。
      • ArrayList list = (ArrayList) paramMap.get("list"):从参数Map中获取名为list的参数,这是批量插入的数据集合。
      • list.forEach(v -> {...}):对每个元素进行操作,调用setFieldValByName方法设置createdAtupdatedAt为当前时间。
    • else部分:处理单条插入的情况,直接给parameter对象设置创建时间和更新时间。
            else if(SqlCommandType.UPDATE.equals(sqlCommandType)){
                // 更新操作
                // 设置更新人和更新时间字段值
                setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
            }
  • else if (SqlCommandType.UPDATE.equals(sqlCommandType)):如果当前SQL是UPDATE操作:
    • 使用setFieldValByName方法将updatedAt字段设置为当前时间。
        }

        // 继续执行原始方法
        return invocation.proceed();
    }
  • 最终通过invocation.proceed()调用被拦截的方法,继续执行原始的数据库操作。
    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):创建MetaObject,用于操作传入对象的元数据。
    • 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);
    }
}
  • setPropertiesplugin方法是Interceptor接口的默认实现,plugin方法用于生成代理对象。

总结

  • 这个拦截器的作用是自动填充createdAtupdatedAt字段,以便在执行INSERT和UPDATE操作时自动记录创建和更新时间。
  • 主要拦截Executorupdate方法,通过判断SQL类型来确定是INSERT还是UPDATE操作,从而设置相应字段。
  • 使用了MyBatis的MetaObject工具类来动态操作参数对象的字段值。

通过这个拦截器,开发者不需要在业务代码中手动设置createdAtupdatedAt,大大减少了重复代码,也保证了这些公共字段的一致性和正确性。

方法二:

这个方法是使用自定义注解来写的,所以要在需要填充的sql上加上这个注解。这个可能更加灵活更加简单把。

公共字段自动填充

技术点:枚举、注解、AOP、反射
创建时间、修改时间、创建人、修改人这4个公共字段。
为mapper方法加注解AutoFill,标识需要进行公共字段自动填充
自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。
在Mapper的方法上接入AutoFill注解。

public enum OperationType {
    更新操作
    UPDATE,
    插入操作
    INSERT
}

@Target(ElementType.METHOD)当前注解加在什么位置
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型: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表达式

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@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("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库操作类型

        //获取到当前被拦截的方法的参数--实体对象	做一个约定,实体对象放第一个
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }

        Object entity = args[0];实体

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        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);

自定义切面:实现公共字段的自动填充

这段代码使用了 Spring AOP(面向切面编程)来实现对数据库操作时,自动填充一些公共字段,例如创建时间、更新时间、创建人、更新人等。接下来,我们逐行解析这段代码,以帮助你理解各个部分的功能和实现逻辑。

代码结构概览

@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,它会在符合条件的数据库操作方法执行之前,通过前置通知 (@Before) 自动对某些公共字段进行填充。

注解解释

  1. @Aspect:表示当前类是一个切面类,用于定义通知和切入点。
  2. @Component:把这个切面类注册为 Spring 容器中的一个组件。
  3. @Slf4j:用来启用日志功能,以方便调试和记录信息。

切入点定义

@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}

解释:

  • @Pointcut:用于定义一个切入点,描述哪些方法需要被切面逻辑拦截。
  • execution(* com.sky.mapper.*.*(..)):匹配 com.sky.mapper 包下的所有类和所有方法,(..) 表示任意参数类型和数量。
  • && @annotation(com.sky.annotation.AutoFill):表示只拦截被 @AutoFill 注解标记的方法。

通过这种定义,只有符合指定包下的类且有 @AutoFill 注解的方法,才会被切面逻辑拦截。

前置通知(Before Advice)

@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
    log.info("开始进行公共字段自动填充...");
    ...
}
  • @Before("autoFillPointCut()"):这是前置通知,表示在切入点所匹配的方法执行之前,执行 autoFill() 方法。
  • JoinPoint joinPoint:JoinPoint 是一个连接点,表示被拦截的方法,允许获取到目标方法的一些信息,比如方法名和参数等。

获取注解和方法信息

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType operationType = autoFill.value();
  1. MethodSignature signature = (MethodSignature) joinPoint.getSignature();:获取当前拦截的方法的签名信息,转换为 MethodSignature 类型。
  2. AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);:获取方法上的 @AutoFill 注解对象。
  3. OperationType operationType = autoFill.value();:获取注解中指定的数据库操作类型(例如 INSERT 或 UPDATE)。

获取方法参数

Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
    return;
}
Object entity = args[0];
  • Object[] args = joinPoint.getArgs();:获取当前被拦截的方法的参数。
  • if (args == null || args.length == 0):如果没有参数,直接返回。
  • Object entity = args[0];:假设第一个参数是实体对象,用于操作数据库。这里有一个约定,即实体对象总是第一个参数。

准备赋值的数据

LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
  • LocalDateTime now = LocalDateTime.now();:获取当前时间,用于填充创建时间和更新时间。
  • Long currentId = BaseContext.getCurrentId();:获取当前操作用户的 ID,用于填充创建人和更新人信息。

根据操作类型进行赋值

插入操作(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):如果数据库操作类型是插入(INSERT)。
  • 通过反射的方式获取实体类中的 setCreateTimesetCreateUsersetUpdateTime 和 setUpdateUser 方法。
  • invoke() 方法用于调用这些 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)。
  • 这里只需填充更新相关的字段,即更新时间和更新人。

小结

这段代码实现了对数据库操作的公共字段自动填充,具体如下:

  • 定义一个切面 AutoFillAspect,用于拦截特定包中的方法,并且方法需要用 @AutoFill 注解进行标记。
  • 使用 AOP 的前置通知在方法执行前进行字段自动填充。
  • 通过反射机制获取实体对象的方法并进行赋值,根据操作类型填充不同的字段。

这使得代码变得更加简洁和可维护,减少了重复的公共字段赋值逻辑,也方便对创建时间、更新时间等公共属性的一致性管理。

以上就是SpringBoot实现字段自动填充的两种方式的详细内容,更多关于SpringBoot字段自动填充的资料请关注脚本之家其它相关文章!

相关文章

  • Nacos框架服务注册实现流程

    Nacos框架服务注册实现流程

    这篇文章主要介绍了SpringCloud服务注册之nacos实现过程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • Flink DataStream基础框架源码分析

    Flink DataStream基础框架源码分析

    这篇文章主要为大家介绍了Flink DataStream基础框架源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 浅谈SpringBoot2.3 新特配置文件属性跟踪

    浅谈SpringBoot2.3 新特配置文件属性跟踪

    这篇文章主要介绍了浅谈SpringBoot2.3 新特配置文件属性跟踪,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • Mybatis 实现打印sql语句的代码

    Mybatis 实现打印sql语句的代码

    这篇文章主要介绍了Mybatis 实现打印sql语句的代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • 浅析如何在SpringBoot中实现数据脱敏

    浅析如何在SpringBoot中实现数据脱敏

    脱敏是指在不改变原数据结构的前提下,通过某种方式处理数据,使数据不能直接暴露用户的真实信息,下面我们就来看看SpringBoot中实现数据脱敏的具体方法吧
    2024-03-03
  • Netty源码分析NioEventLoop初始化线程选择器创建

    Netty源码分析NioEventLoop初始化线程选择器创建

    这篇文章主要介绍了Netty源码分析NioEventLoop初始化线程选择器创建,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • FastJSON字段智能匹配踩坑的解决

    FastJSON字段智能匹配踩坑的解决

    这篇文章主要介绍了FastJSON字段智能匹配踩坑的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Mybatis-plus一对多分页数据条数不正确的处理

    Mybatis-plus一对多分页数据条数不正确的处理

    这篇文章主要介绍了Mybatis-plus一对多分页数据条数不正确的处理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • SpringBoot+websocket实现消息对话功能

    SpringBoot+websocket实现消息对话功能

    WebSocket是一种在Web应用程序中实现实时双向通信的技术,它可以用于在线游戏、在线聊天、推送通知、实时监控等,并且比传统的轮询技术更加高效和可靠,本文就给大家介绍基于SpringBoot+websocket实现消息对话功能,感兴趣的小伙伴可以自己动手试一试
    2023-09-09
  • Java数组高级算法与Arrays类常见操作小结【排序、查找】

    Java数组高级算法与Arrays类常见操作小结【排序、查找】

    这篇文章主要介绍了Java数组高级算法与Arrays类常见操作,结合实例形式总结分析了Java数组常见的排序算法、查找算法相关原理、实现与使用技巧,需要的朋友可以参考下
    2019-03-03

最新评论