You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MyBatis 3.0.2升级后,时间填充插件参数重赋值问题咨询

MyBatis 3.x 时间填充插件参数适配方案

针对升级MyBatis Spring Boot Starter到3.0.2后参数结构变化导致的冗余代码问题,你可以通过通用参数提取逻辑兼容旧版、新版+MyBatis Dynamic SQL的场景,避免重复分支判断。

核心思路

不管invocation.getArgs()返回的参数数量是多少,只需要聚焦两件事:

  1. 定位到需要填充时间的业务实体对象(可能是直接参数,也可能被包装在ParamMap里)
  2. 修改实体的时间字段(如果是ParamMap包装的对象,因为是引用传递,修改实体本身就会同步到ParamMap中,无需额外对参数数组赋值)

代码实现

1. 统一参数提取工具方法

把参数识别逻辑封装成工具方法,避免在拦截器里写冗余的分支判断:

import org.apache.ibatis.binding.MapperMethod;
import java.util.Map;

private Object getTargetEntity(Object[] args) {
    if (args == null || args.length == 0) {
        return null;
    }

    // 遍历所有参数,找到业务实体对象
    for (Object arg : args) {
        if (arg == null) {
            continue;
        }
        // 处理MyBatis Dynamic SQL或新版MyBatis的ParamMap包装场景
        if (arg instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) arg;
            // 优先取带@Param注解的参数,否则取第一个非框架类的参数
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                Object value = entry.getValue();
                if (value != null && !value.getClass().getName().startsWith("org.apache.ibatis")) {
                    return value;
                }
            }
            return null;
        }
        // 处理旧版直接传实体的场景,或者新版未被包装的参数
        if (!arg.getClass().getName().startsWith("org.apache.ibatis")) {
            return arg;
        }
    }
    return null;
}

2. 拦截器核心逻辑

在intercept方法中调用工具方法提取实体,然后填充时间字段:

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Properties;

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class TimeFillInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取MappedStatement和参数数组
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object[] params = new Object[]{invocation.getArgs()[1]};
        // 提取目标实体
        Object entity = getTargetEntity(params);

        if (entity != null) {
            LocalDateTime now = LocalDateTime.now();
            SqlCommandType commandType = ms.getSqlCommandType();

            // 填充时间字段(两种方式二选一)
            // 方式1:实体实现统一接口(推荐,避免反射)
            if (entity instanceof TimeAble) {
                TimeAble timeAble = (TimeAble) entity;
                if (SqlCommandType.INSERT.equals(commandType)) {
                    timeAble.setCreateTime(now);
                }
                timeAble.setUpdateTime(now);
            }
            // 方式2:反射处理无统一接口的场景
            else {
                try {
                    // 插入时设置createTime
                    if (SqlCommandType.INSERT.equals(commandType)) {
                        Method setCreateTime = entity.getClass().getMethod("setCreateTime", LocalDateTime.class);
                        setCreateTime.invoke(entity, now);
                    }
                    // 所有更新/插入操作都设置updateTime
                    Method setUpdateTime = entity.getClass().getMethod("setUpdateTime", LocalDateTime.class);
                    setUpdateTime.invoke(entity, now);
                } catch (NoSuchMethodException e) {
                    // 实体无对应时间字段,跳过处理
                }
            }
        }

        // 执行原方法
        return invocation.proceed();
    }

    // 工具方法:提取目标实体(实现代码同上)
    private Object getTargetEntity(Object[] args) {
        // 工具方法代码
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置时间格式等参数,按需实现
    }

    // 统一时间字段接口(推荐实体类实现)
    public interface TimeAble {
        void setCreateTime(LocalDateTime createTime);
        void setUpdateTime(LocalDateTime updateTime);
    }
}

优化说明

  1. 消除冗余分支:通过通用参数提取方法,自动适配旧版直接传实体、新版ParamMap包装、MyBatis Dynamic SQL等多种场景,无需单独写分支判断
  2. 优先接口实现:定义TimeAble接口让业务实体实现,避免反射带来的性能损耗和代码冗余
  3. 准确判断操作类型:通过MappedStatement的SqlCommandType判断是插入还是更新操作,比判断方法名更可靠
  4. 无需额外赋值:ParamMap中存储的是实体对象的引用,修改实体本身就会同步到ParamMap中,不需要对invocation.getArgs()做额外赋值操作

内容的提问来源于stack exchange,提问作者courage

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 11:09:56