MyBatis 3.0.2升级后,时间填充插件参数重赋值问题咨询
MyBatis 3.x 时间填充插件参数适配方案
针对升级MyBatis Spring Boot Starter到3.0.2后参数结构变化导致的冗余代码问题,你可以通过通用参数提取逻辑兼容旧版、新版+MyBatis Dynamic SQL的场景,避免重复分支判断。
核心思路
不管invocation.getArgs()返回的参数数量是多少,只需要聚焦两件事:
- 定位到需要填充时间的业务实体对象(可能是直接参数,也可能被包装在
ParamMap里) - 修改实体的时间字段(如果是
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); } }
优化说明
- 消除冗余分支:通过通用参数提取方法,自动适配旧版直接传实体、新版
ParamMap包装、MyBatis Dynamic SQL等多种场景,无需单独写分支判断 - 优先接口实现:定义
TimeAble接口让业务实体实现,避免反射带来的性能损耗和代码冗余 - 准确判断操作类型:通过
MappedStatement的SqlCommandType判断是插入还是更新操作,比判断方法名更可靠 - 无需额外赋值:
ParamMap中存储的是实体对象的引用,修改实体本身就会同步到ParamMap中,不需要对invocation.getArgs()做额外赋值操作
内容的提问来源于stack exchange,提问作者courage
相关产品推荐
相关产品推荐

