如何用易语言做网站,自己做网站是否要买云主机,网站开发需要什么关键技术,建设通相似网站MyBatis-Plus 提供了一个便捷的自动填充功能#xff0c;用于在插入或更新数据时自动填充某些字段#xff0c;如创建时间、更新时间等。
官方文档#xff1a;https://baomidou.com/guides/auto-fill-field/ 一、引言
在日常开发中#xff0c;我们经常需要处理一些公共字段…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开始推荐使用strictInsertFill和strictUpdateFill方法它们会进行严格的类型检查避免类型不匹配的问题。四、完整示例用户管理场景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 BaseMapperUser { }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 ThreadLocalUserContext 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 ServiceImplUserMapper, 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(WrapperT updateWrapper)时自动填充仍然生效性能考虑自动填充会增加一定的性能开销但对于大多数应用来说可以忽略不计六、 常见问题解决问题1填充不生效检查MetaObjectHandler是否被Spring管理添加Component注解检查实体类字段是否添加了TableField(fill ...)注解检查字段名是否与strictInsertFill方法中的参数一致问题2类型转换错误确保填充值的类型与实体类字段类型一致使用strictInsertFill方法进行严格的类型检查问题3填充时机不对插入操作触发insertFill方法更新操作触发updateFill方法使用saveOrUpdate方法时会根据记录是否存在决定调用哪个方法七、工作实战7.1MyBatis 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(); // 创建字段列表 ListField 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) { // 当前实现为空可根据需要添加属性配置逻辑 } }