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字段自动填充的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot实现国际化i18n详解

    SpringBoot实现国际化i18n详解

    国际化(Internationalization,简称i18n)是指在软件应用中支持多种语言和文化的能力,本文将介绍如何在Spring Boot应用中实现国际化,需要的可以参考下
    2024-12-12
  • 通过实例了解java checked和unchecked异常

    通过实例了解java checked和unchecked异常

    这篇文章主要介绍了通过实例了解checked和unchecked异常,Java异常分为两种类型,checked异常和unchecked异常,另一种叫法是异常和错误。下面小编就带大家来一起学习一下吧
    2019-06-06
  • Java的jmap命令的具体使用

    Java的jmap命令的具体使用

    jmap是JDK提供的一个可以生成Java虚拟机的堆转储快照dump文件的命令行工具,本文主要介绍了Java的jmap命令的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • SpringBoot+Vue项目部署实现传统方式

    SpringBoot+Vue项目部署实现传统方式

    我们在进行前后端分离开发的时候,一般是将前端项目部署到nginx服务器上,与后端项目分开部署,这篇文章主要给大家介绍了关于SpringBoot+Vue项目部署实现传统方式的相关资料,需要的朋友可以参考下
    2024-01-01
  • Java中的String对象数据类型全面解析

    Java中的String对象数据类型全面解析

    首先String不属于8种基本数据类型,String是一个对象,因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性
    2012-11-11
  • Java的volatile和sychronized底层实现原理解析

    Java的volatile和sychronized底层实现原理解析

    文章详细介绍了Java中的synchronized和volatile关键字的底层实现原理,包括字节码层面、JVM层面的实现细节,以及锁的类型和MESI协议在多核处理器中的作用,文章还探讨了synchronized和volatile的区别,以及如何通过Atomic类来实现更细粒度的原子操作,感兴趣的朋友一起看看吧
    2025-03-03
  • 详细分析Java并发集合LinkedBlockingQueue的用法

    详细分析Java并发集合LinkedBlockingQueue的用法

    这篇文章主要介绍了详细分析Java并发集合LinkedBlockingQueue的用法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • Java的访问修饰符与变量的作用域讲解

    Java的访问修饰符与变量的作用域讲解

    这篇文章主要介绍了Java的访问修饰符与变量的作用域讲解,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • 一文掌握spring cloud gateway(总结篇)

    一文掌握spring cloud gateway(总结篇)

    Spring Cloud Gateway是Spring Cloud的全新项目,该项目是基于Spring 5.0,Spring WebFlux和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式,本文通过实例代码总结介绍spring cloud gateway的相关知识,感兴趣的朋友一起看看吧
    2024-12-12
  • SpringMVC中RequestMapping注解(作用、出现的位置、属性)

    SpringMVC中RequestMapping注解(作用、出现的位置、属性)

    这篇文章主要介绍了SpringMVC中RequestMapping注解(作用、出现的位置、属性),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01

最新评论