java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot字段自动填充

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

作者: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})
})
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];
        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);
    }
}

总结

通过这个拦截器,开发者不需要在业务代码中手动设置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(){}

解释:

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

前置通知(Before Advice)

@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();
  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];

准备赋值的数据

LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();

根据操作类型进行赋值

插入操作(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();
    }
}

更新操作(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();
    }
}

小结

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

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

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

您可能感兴趣的文章:
阅读全文