news 2026/3/27 1:36:25

MyBatis-Plus自动填充字段:优雅处理创建时间和更新时间

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis-Plus自动填充字段:优雅处理创建时间和更新时间

MyBatis-Plus 提供了一个便捷的自动填充功能,用于在插入或更新数据时自动填充某些字段,如创建时间、更新时间等。

官方文档:https://baomidou.com/guides/auto-fill-field/

一、引言

在日常开发中,我们经常需要处理一些公共字段的自动填充,比如数据的创建时间、更新时间、创建人、更新人等。手动为这些字段赋值不仅繁琐,而且容易遗漏。MyBatis-Plus作为MyBatis的增强工具,提供了强大的自动填充功能,让我们能够优雅地解决这个问题。

二、什么是自动填充?

自动填充是MyBatis-Plus的一个核心功能,它允许我们在执行插入或更新操作时,自动为特定字段填充值。最常见的应用场景包括:

  • 创建时间:数据插入时自动填充当前时间

  • 更新时间:数据更新时自动填充当前时间

  • 操作人信息:自动填充当前登录用户ID或姓名

  • 逻辑删除标记:自动填充删除状态

三、实现方式详解

MyBatis-Plus提供了两种实现自动填充的方式:注解配置和自定义处理器。

3.1方式一:使用@TableField注解(简单场景)

对于简单的固定值填充,可以直接在实体类字段上使用@TableField注解:

import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import java.time.LocalDateTime; public class User { private Long id; private String name; // 插入时自动填充固定值 @TableField(fill = FieldFill.INSERT, value = "'system'") private String createBy; // 插入和更新时自动填充固定值 @TableField(fill = FieldFill.INSERT_UPDATE, value = "'admin'") private String updateBy; }

支持的操作类型:

  • FieldFill.DEFAULT:默认不处理

  • FieldFill.INSERT:插入时填充

  • FieldFill.UPDATE:更新时填充

  • FieldFill.INSERT_UPDATE:插入和更新时都填充

3.2方式二:实现MetaObjectHandler接口(推荐)

对于需要动态计算的复杂场景,实现MetaObjectHandler接口是更灵活的选择:

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component public class MyMetaObjectHandler implements MetaObjectHandler { /** * 插入时自动填充 */ @Override public void insertFill(MetaObject metaObject) { // 填充创建时间 this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 填充更新时间(插入时也需要) this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 填充创建人(从线程上下文或SecurityContext中获取) String currentUser = getCurrentUser(); this.strictInsertFill(metaObject, "createBy", String.class, currentUser); // 填充更新人 this.strictInsertFill(metaObject, "updateBy", String.class, currentUser); // 填充其他业务字段 this.strictInsertFill(metaObject, "tenantId", Long.class, getTenantId()); } /** * 更新时自动填充 */ @Override public void updateFill(MetaObject metaObject) { // 只填充更新时间 this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 填充更新人 String currentUser = getCurrentUser(); this.strictUpdateFill(metaObject, "updateBy", String.class, currentUser); } /** * 获取当前用户(示例方法) */ private String getCurrentUser() { // 实际项目中可以从SecurityContext、JWT token或ThreadLocal中获取 return "admin"; } /** * 获取租户ID(多租户场景) */ private Long getTenantId() { return 1L; } }

注意:从MyBatis-Plus 3.3.0开始,推荐使用strictInsertFillstrictUpdateFill方法,它们会进行严格的类型检查,避免类型不匹配的问题。

四、完整示例:用户管理场景

4.1 实体类定义

import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("sys_user") public class User { @TableId(type = IdType.AUTO) private Long id; private String username; private String email; private Integer status; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT) private String createBy; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT_UPDATE) private String updateBy; @TableLogic @TableField(fill = FieldFill.INSERT) private Integer deleted; }

4.2 Mapper接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper<User> { }

4.3 自定义填充处理器

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Slf4j @Component public class AutoFillMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("开始插入填充..."); // 方法1: 根据属性类型自动匹配(推荐) this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 方法2: 直接设置值(不推荐,缺少类型检查) // this.setFieldValByName("createTime", LocalDateTime.now(), metaObject); // 从ThreadLocal获取当前用户(示例) UserContext currentUser = UserContextHolder.get(); if (currentUser != null) { this.strictInsertFill(metaObject, "createBy", String.class, currentUser.getUsername()); this.strictInsertFill(metaObject, "updateBy", String.class, currentUser.getUsername()); } // 逻辑删除字段默认值 this.strictInsertFill(metaObject, "deleted", Integer.class, 0); } @Override public void updateFill(MetaObject metaObject) { log.info("开始更新填充..."); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); UserContext currentUser = UserContextHolder.get(); if (currentUser != null) { this.strictUpdateFill(metaObject, "updateBy", String.class, currentUser.getUsername()); } } } // 用户上下文持有器(示例) class UserContextHolder { private static final ThreadLocal<UserContext> holder = new ThreadLocal<>(); public static void set(UserContext userContext) { holder.set(userContext); } public static UserContext get() { return holder.get(); } public static void clear() { holder.remove(); } } class UserContext { private String username; private Long userId; // getters and setters }

4.4 业务层使用

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { @Override @Transactional public boolean saveUser(User user) { // 设置当前用户上下文(通常在拦截器中设置) UserContext context = new UserContext(); context.setUsername("currentUser"); UserContextHolder.set(context); try { // 插入操作,自动填充字段会自动处理 return this.save(user); } finally { // 清理线程变量 UserContextHolder.clear(); } } @Override @Transactional public boolean updateUser(User user) { UserContext context = new UserContext(); context.setUsername("currentUser"); UserContextHolder.set(context); try { // 更新操作,自动填充字段会自动处理 return this.updateById(user); } finally { UserContextHolder.clear(); } } }

五、高级用法和注意事项

5.1 条件填充

有时我们需要根据条件决定是否填充:

@Override public void insertFill(MetaObject metaObject) { // 只有字段为空时才填充 if (metaObject.getValue("createTime") == null) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); } // 根据实体类的某个属性决定是否填充 User user = (User) metaObject.getOriginalObject(); if (user != null && "admin".equals(user.getUserType())) { this.strictInsertFill(metaObject, "adminFlag", String.class, "Y"); } }

5.2 多租户场景

@Override public void insertFill(MetaObject metaObject) { // 自动填充租户ID Long tenantId = TenantContext.getCurrentTenantId(); if (tenantId != null) { this.strictInsertFill(metaObject, "tenantId", Long.class, tenantId); } }

5.3 注意事项

  • 字段默认值冲突:如果数据库字段有默认值,且实体类字段也有自动填充,可能会产生冲突

  • 批量操作:批量插入和更新同样支持自动填充

  • 局部更新:使用update(Wrapper<T> updateWrapper)时,自动填充仍然生效

  • 性能考虑:自动填充会增加一定的性能开销,但对于大多数应用来说可以忽略不计

六、 常见问题解决

问题1:填充不生效

  • 检查MetaObjectHandler是否被Spring管理(添加@Component注解)

  • 检查实体类字段是否添加了@TableField(fill = ...)注解

  • 检查字段名是否与strictInsertFill方法中的参数一致

问题2:类型转换错误

  • 确保填充值的类型与实体类字段类型一致

  • 使用strictInsertFill方法进行严格的类型检查

问题3:填充时机不对

  • 插入操作触发insertFill方法

  • 更新操作触发updateFill方法

  • 使用saveOrUpdate方法时,会根据记录是否存在决定调用哪个方法

七、工作实战

7.1‌MyBatis Plus‌自动填充

package com.zm.platform.framework.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.zm.platform.constant.ValidFlagConstant; import com.zm.platform.modular.loginUser.service.LoginUserService; import com.zm.platform.modular.user.entity.User; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import java.util.Date; import java.util.Objects; @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Lazy @Autowired private LoginUserService loginUserService; @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); User user = new User(); try { user = loginUserService.getLoginUser().getUser(); } catch (Exception e) { } this.fillStrategy(metaObject, "createName", user.getRealName()); this.fillStrategy(metaObject, "createId", user.getId()); this.fillStrategy(metaObject, "createTime", new Date()); this.fillStrategy(metaObject, "valiflag", ValidFlagConstant.EFFECTIVE); this.fillStrategy(metaObject, "orgId", user.getOrgId()); this.fillStrategy(metaObject, "orgName", user.getOrgName()); this.fillStrategy(metaObject, "updateName", user.getRealName()); this.fillStrategy(metaObject, "updateId", user.getId()); this.fillStrategy(metaObject, "updtId", user.getId()); this.fillStrategy(metaObject, "updtName", user.getRealName()); this.fillStrategy(metaObject, "updateTime", new Date()); this.fillStrategy(metaObject, "updtTime", new Date()); // this.setFieldValByName("createName", user.getRealName() ,metaObject); // this.setFieldValByName( "createId", user.getId() ,metaObject); // this.setFieldValByName("orgId",user.getOrgId() ,metaObject); // this.setFieldValByName("orgName",user.getOrgName() ,metaObject); // this.setFieldValByName( "createTime", new Date() ,metaObject); // this.setFieldValByName( "valiflag", ValidFlagConstant.EFFECTIVE ,metaObject); } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); User user = new User(); try { user = loginUserService.getLoginUser().getUser(); } catch (Exception e) { } this.setFieldValByName("updateName", user.getRealName(), metaObject); this.setFieldValByName("updateId", user.getId(), metaObject); this.setFieldValByName("updtId", user.getId(), metaObject); this.setFieldValByName("updtName", user.getRealName(), metaObject); this.setFieldValByName("updateTime", new Date(), metaObject); this.setFieldValByName("updtTime", new Date(), metaObject); } }

7.2MyBatis自动填充

package com.jingdianjichi.subject.infra.config; import com.jingdianjichi.subject.common.util.LoginUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.*; /** * MyBatis拦截器,用于自动填充实体类中的创建人、创建时间、更新人、更新时间等公共字段 * 在执行INSERT或UPDATE操作时自动设置相关字段值 */ @Component @Slf4j @Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class })}) public class MybatisInterceptor implements Interceptor { /** * 拦截方法,在MyBatis执行更新操作前被调用 * @param invocation 调用信息对象,包含被拦截的方法信息和参数 * @return 执行结果 * @throws Throwable 异常信息 */ @Override public Object intercept(Invocation invocation) throws Throwable { // 获取MappedStatement对象,包含SQL语句的信息 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; // 获取SQL命令类型(INSERT、UPDATE、DELETE、SELECT等) SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); // 获取SQL参数对象 Object parameter = invocation.getArgs()[1]; // 如果参数为空,则直接执行原方法 if (parameter == null) { return invocation.proceed(); } // 获取当前登录用户的ID String loginId = LoginUtil.getLoginId(); // 如果用户未登录或登录ID为空,则直接执行原方法 if (StringUtils.isBlank(loginId)) { return invocation.proceed(); } // 如果是插入或更新操作,则处理实体属性填充 if (SqlCommandType.INSERT == sqlCommandType || SqlCommandType.UPDATE == sqlCommandType) { replaceEntityProperty(parameter, loginId, sqlCommandType); } // 继续执行原方法 return invocation.proceed(); } /** * 根据参数类型替换实体属性值 * @param parameter SQL参数对象 * @param loginId 当前登录用户ID * @param sqlCommandType SQL命令类型 */ private void replaceEntityProperty(Object parameter, String loginId, SqlCommandType sqlCommandType) { // 如果参数是Map类型,则遍历Map中的每个值进行处理 if (parameter instanceof Map) { replaceMap((Map) parameter, loginId, sqlCommandType); } else { // 如果不是Map类型,则直接处理该对象 replace(parameter, loginId, sqlCommandType); } } /** * 处理Map类型的参数 * @param parameter Map参数 * @param loginId 当前登录用户ID * @param sqlCommandType SQL命令类型 */ private void replaceMap(Map parameter, String loginId, SqlCommandType sqlCommandType) { // 遍历Map中的所有值,对每个值进行处理 for (Object val : parameter.values()) { replace(val, loginId, sqlCommandType); } } /** * 根据SQL命令类型决定处理方式 * @param parameter 参数对象 * @param loginId 当前登录用户ID * @param sqlCommandType SQL命令类型 */ private void replace(Object parameter, String loginId, SqlCommandType sqlCommandType) { // 如果是插入操作,则处理插入相关的字段 if (SqlCommandType.INSERT == sqlCommandType) { dealInsert(parameter, loginId); } else { // 如果是更新操作,则处理更新相关的字段 dealUpdate(parameter, loginId); } } /** * 处理更新操作,自动填充更新人和更新时间字段 * @param parameter 参数对象 * @param loginId 当前登录用户ID */ private void dealUpdate(Object parameter, String loginId) { // 获取对象的所有字段(包括父类的字段) Field[] fields = getAllFields(parameter); // 遍历所有字段 for (Field field : fields) { try { // 设置字段可访问(即使是private字段也可以访问) field.setAccessible(true); // 获取字段当前值 Object o = field.get(parameter); // 如果字段已经有值,则跳过不处理 if (Objects.nonNull(o)) { field.setAccessible(false); continue; } // 如果字段名为updateBy,则设置为当前登录用户ID if ("updateBy".equals(field.getName())) { field.set(parameter, loginId); field.setAccessible(false); } // 如果字段名为updateTime,则设置为当前时间 else if ("updateTime".equals(field.getName())) { field.set(parameter, new Date()); field.setAccessible(false); } // 其他字段关闭访问权限 else { field.setAccessible(false); } } catch (Exception e) { // 记录错误日志 log.error("处理更新操作时发生错误:{}", e.getMessage(), e); } } } /** * 处理插入操作,自动填充创建人、创建时间、删除标识等字段 * @param parameter 参数对象 * @param loginId 当前登录用户ID */ private void dealInsert(Object parameter, String loginId) { // 获取对象的所有字段(包括父类的字段) Field[] fields = getAllFields(parameter); // 遍历所有字段 for (Field field : fields) { try { // 设置字段可访问(即使是private字段也可以访问) field.setAccessible(true); // 获取字段当前值 Object o = field.get(parameter); // 如果字段已经有值,则跳过不处理 if (Objects.nonNull(o)) { field.setAccessible(false); continue; } // 如果字段名为isDeleted(删除标识),则设置为0(未删除) if ("isDeleted".equals(field.getName())) { field.set(parameter, 0); field.setAccessible(false); } // 如果字段名为createdBy(创建人),则设置为当前登录用户ID else if ("createdBy".equals(field.getName())) { field.set(parameter, loginId); field.setAccessible(false); } // 如果字段名为createdTime(创建时间),则设置为当前时间 else if ("createdTime".equals(field.getName())) { field.set(parameter, new Date()); field.setAccessible(false); } // 其他字段关闭访问权限 else { field.setAccessible(false); } } catch (Exception e) { // 记录错误日志 log.error("处理插入操作时发生错误:{}", e.getMessage(), e); } } } /** * 获取对象的所有字段,包括父类中的字段 * @param object 对象实例 * @return 对象的所有字段数组 */ private Field[] getAllFields(Object object) { // 获取对象的Class Class<?> clazz = object.getClass(); // 创建字段列表 List<Field> fieldList = new ArrayList<>(); // 循环获取当前类及其父类的所有字段 while (clazz != null) { // 将当前类声明的所有字段添加到列表中 fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); // 获取父类继续循环 clazz = clazz.getSuperclass(); } // 将字段列表转换为数组并返回 Field[] fields = new Field[fieldList.size()]; fieldList.toArray(fields); return fields; } /** * 插件包装方法 * @param target 目标对象 * @return 包装后的对象 */ @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } /** * 设置插件属性 * @param properties 属性配置 */ @Override public void setProperties(Properties properties) { // 当前实现为空,可根据需要添加属性配置逻辑 } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/12 10:02:47

RD-Agent终极指南:3步实现AI驱动的自动化数据科学研发

RD-Agent终极指南&#xff1a;3步实现AI驱动的自动化数据科学研发 【免费下载链接】RD-Agent Research and development (R&D) is crucial for the enhancement of industrial productivity, especially in the AI era, where the core aspects of R&D are mainly focu…

作者头像 李华
网站建设 2026/3/21 19:24:21

鸿蒙Electron应用工程化与性能优化实战:从开发到上线全链路

随着鸿蒙 OS 生态的持续完善&#xff0c;前端开发者如何低门槛切入鸿蒙生态成为热门话题。Electron 作为成熟的跨端桌面应用框架&#xff0c;可借助鸿蒙 OS 的 Linux 兼容层实现无缝运行&#xff0c;无需学习 ArkTS 即可快速开发鸿蒙桌面应用。 本文将从环境搭建、核心功能开发…

作者头像 李华
网站建设 2026/3/26 13:45:03

文档备份软件哪款最好用?2025超好用的六款文档备份软件推荐

电脑文件备份已经成为企业与个人用户不可或缺的数据保护措施。面对硬盘故障、病毒攻击、人为误操作等种种风险&#xff0c;选择一款可靠高效的备份软件显得尤为重要。本文将为您盘点市面上几款备受推崇的电脑文件备份软件&#xff0c;帮助您为珍贵数据挑选最合适的安全卫士。一…

作者头像 李华
网站建设 2026/3/24 5:37:10

解决 keil 中flash download failed的问题

问题就是编译通过之后烧录显示这个问题&#xff0c;问题就是flash下载失败我的问题就是在debug/setting中没有加载适配算法&#xff0c;如下图点击添加相应的算法即可但是这里面有时没有需要的算法&#xff0c;我们打开C:\Keil_v5\ARM\PACK\GigaDevice\GD32F4xx_DFP\3.4.0\Flas…

作者头像 李华
网站建设 2026/3/26 11:06:26

DeepSeek-VL2终极部署指南:从零构建企业级多模态AI系统

DeepSeek-VL2终极部署指南&#xff1a;从零构建企业级多模态AI系统 【免费下载链接】deepseek-vl2 探索视觉与语言融合新境界的DeepSeek-VL2&#xff0c;以其先进的Mixture-of-Experts架构&#xff0c;实现图像理解与文本生成的飞跃&#xff0c;适用于视觉问答、文档解析等多场…

作者头像 李华
网站建设 2026/3/25 10:37:13

汇编语言全接触-23.系统托盘中的快捷图标

本课中&#xff0c;我们将学习如何把小图标放到系统托盘中去以及如何创建和使用弹出式菜单。 理论&#xff1a;系统托盘是指任务条中的一个方形区域&#xff0c;在该区域中可以放入一些小图标&#xff0c;通常您可以在此处看到系统提供的最新时间。您自己当然也可以把快捷小图标…

作者头像 李华