← 返回文章列表

MyBatis 字段自动填充

mybatisjava

用 AOP 切面统一维护 create_time、update_time 等公共字段。

MyBatis 字段自动填充(AutoFill)

场景

在数据库的添加修改操作时,需要统一维护以下字段:

  • create_time - 创建时间
  • create_user - 创建人
  • update_time - 更新时间
  • update_user - 更新人

手动在每个方法中设置容易遗漏,使用 AOP 切面实现自动填充

核心思路

  1. 创建枚举类定义操作类型(INSERT / UPDATE)
  2. 创建注解 @AutoFill 标记需要拦截的方法
  3. 创建切面类 AutoFillAspect,在方法执行前自动填充字段
  4. 在 Mapper 方法上添加 @AutoFill 注解

一、定义枚举类

package com.sky.annotation;

/**
 * 数据库操作类型
 */
public enum OperationType {
    INSERT,   // 插入操作
    UPDATE    // 更新操作
}

二、定义注解

package com.sky.annotation;

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 {
    OperationType value();  // 指定操作类型
}

三、定义常量类

package com.sky.constant;

/**
 * 自动填充字段名常量
 */
public class AutoFillConstant {
    public static final String SET_CREATE_TIME = "setCreateTime";
    public static final String SET_CREATE_USER = "setCreateUser";
    public static final String SET_UPDATE_TIME = "setUpdateTime";
    public static final String SET_UPDATE_USER = "setUpdateUser";
}

四、切面类实现

package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.annotation.OperationType;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自动填充切面类
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点:拦截 mapper 包下所有方法,且方法上有 @AutoFill 注解
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {}

    /**
     * 前置通知:在方法执行前填充字段
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始自动填充字段...");

        // 1. 获取操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AutoFill autoFill = method.getAnnotation(AutoFill.class);
        OperationType operationType = autoFill.value();

        // 2. 获取方法参数(实体对象)
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) return;
        Object entity = args[0];  // 第一个参数是实体对象

        // 3. 准备填充的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();  // 获取当前登录用户ID

        // 4. 根据操作类型填充字段
        if (operationType == OperationType.INSERT) {
            // INSERT:填充 4 个字段
            try {
                Method setCreateTime = entity.getClass().getMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getMethod(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) {
                log.error("自动填充失败", e);
            }
        } else if (operationType == OperationType.UPDATE) {
            // UPDATE:填充 2 个字段
            try {
                Method setUpdateTime = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                log.error("自动填充失败", e);
            }
        }

        log.info("字段填充完成");
    }
}

五、在 Mapper 方法上使用

@Mapper
public interface EmployeeMapper {

    @AutoFill(OperationType.INSERT)
    void insert(Employee employee);

    @AutoFill(OperationType.UPDATE)
    void update(Employee employee);
}

六、实体类示例

@Data
public class Employee {
    private Long id;
    private String name;
    private String phone;

    // 自动填充的字段
    private LocalDateTime createTime;
    private Long createUser;
    private LocalDateTime updateTime;
    private Long updateUser;
}

工作流程图

┌─────────────┐     ┌──────────────┐     ┌────────────────┐
│  调用方法    │ ──► │ @AutoFill   │ ──► │  AutoFillAspect│
│ insert(emp) │     │  标记方法    │     │  切面拦截      │
└─────────────┘     └──────────────┘     └───────┬────────┘
                                                │
                                                ▼
                                       ┌────────────────┐
                                       │  反射调用 setter│
                                       │  填充字段值    │
                                       └────────────────┘