SpringBoot - 公共字段自动填充的6种方案 - 实践

在这里插入图片描述

01痛点速览

// 每写一个 Service 就要来一遍
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
order.setCreateUser(getCurrentUser());
order.setUpdateUser(getCurrentUser());

总结:

  1. Ctrl C + Ctrl V 到手抽筋
  2. 一改字段,全局爆炸
  3. 一不留神就漏填

02方案 1MyBatis-Plus 元对象处理器

最香基础款

2.1 核心代码

@Slf4j
@Component
public class AutoFillHandler
implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 新增时填充
strictInsertFill(metaObject, "createTime", LocalDateTime.class,
LocalDateTime.now());
strictInsertFill(metaObject, "createUser", String.class,
getCurrentUser());
strictInsertFill(metaObject, "updateTime", LocalDateTime.class,
LocalDateTime.now());
strictInsertFill(metaObject, "updateUser", String.class,
getCurrentUser());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时只刷新 update*
strictUpdateFill(metaObject, "updateTime", LocalDateTime.class,
LocalDateTime.now());
strictUpdateFill(metaObject, "updateUser", String.class,
getCurrentUser());
}
/** 从 Security 上下文里拿登录人 */
private String getCurrentUser() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.orElse("system");
}
}

注解:

  1. 实现 MetaObjectHandler,MyBatis-Plus 会自动回调
  2. strictInsertFill 只在 insert 语句里生效
  3. 使用 Optional 防 NPE,稳!

2.2 实体类配上注解

@Data
public abstract class BaseEntity
{
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private String createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateUser;
}
@Entity
@Table(name = "t_order")
public class Order
extends BaseEntity {
}

03方案 2:AOP 切面

专治各种不服

3.1 自定义注解,声明式零侵入

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
// INSERT / UPDATE
}
public enum OperationType {
INSERT, UPDATE
}

3.2 切面逻辑

@Aspect
@Component
@Slf4j
public class AutoFillAspect
{
@Around("@annotation(autoFill)")
public Object around(ProceedingJoinPoint pjp, AutoFill autoFill) throws Throwable {
Object[] args = pjp.getArgs();
for (Object arg : args) {
if (arg instanceof BaseEntity) {
fill((BaseEntity) arg, autoFill.value());
}
}
return pjp.proceed(args);
}
private void fill(BaseEntity entity, OperationType type) {
String user = getCurrentUser();
LocalDateTime now = LocalDateTime.now();
if (type == OperationType.INSERT) {
entity.setCreateTime(now);
entity.setCreateUser(user);
}
entity.setUpdateTime(now);
entity.setUpdateUser(user);
}
private String getCurrentUser() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(ServletRequestAttributes.class::
cast)
.map(ServletRequestAttributes::getRequest)
.map(req -> req.getHeader("X-User-Id"))
.orElse("system");
}
}

注解:

  1. @Around 拦截所有标注 @AutoFill 的方法
  2. 统一在进入方法前就把字段塞满
  3. 支持多参数批量处理,一行注解搞定

04方案 3:分布式 ID + 多数据源

4.1 Snowflake 发号器

@Configuration
public class SnowflakeConfig
{
@Bean
public SnowflakeIdWorker idWorker(@Value("${snowflake.workerId}") long workerId,
@Value("${snowflake.dataCenterId}") long dataCenterId) {
return new SnowflakeIdWorker(workerId, dataCenterId);
}
}

4.2 多数据源下自动填充

public class MultiDataSourceAutoFillHandler
extends MetaObjectHandler {
@Autowired
private SnowflakeIdWorker idWorker;
@Override
public void insertFill(MetaObject metaObject) {
super.insertFill(metaObject);
// 分布式主键
strictInsertFill(metaObject, "id", Long.class, idWorker.
nextId());
}
}

05方案 4:性能优化三板斧

5.1 ThreadLocal 缓存当前用户

public class UserContextHolder
{
private static final ThreadLocal<
String> holder = new ThreadLocal<
>();
public static void set(String user) { holder.set(user);
}
public static String get() {
return holder.get();
}
public static void clear() { holder.remove();
}
}
public class UserInterceptor
implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
UserContextHolder.set(req.getHeader("X-User-Id"));
return true;
}
@Override
public void afterCompletion(...) {
UserContextHolder.clear();
// 防止内存泄漏
}
}

5.2 批量操作统一时间

@Transactional
public void batchInsert(List<
Order> orders) {
String user = UserContextHolder.get();
LocalDateTime now = LocalDateTime.now();
orders.forEach(o ->
{
o.setCreateTime(now);
o.setCreateUser(user);
o.setUpdateTime(now);
o.setUpdateUser(user);
});
orderMapper.batchInsert(orders);
}

06方案 5:监控 + 审计

6.1 切面记录操作日志

@Aspect
@Component
public class OperationLogAspect
{
@AfterReturning("@annotation(autoFill)")
public void log(AutoFill autoFill) {
LogEntry log = new LogEntry();
log.setOperator(UserContextHolder.get());
log.setOperation(autoFill.value().name());
log.setTime(LocalDateTime.now());
logService.save(log);
}
}

避坑指南

在这里插入图片描述

总结

MyBatis-Plus 打底,AOP 做加持,Snowflake 管 ID,ThreadLocal 提性能,切面搞审计

posted @ 2025-08-23 20:04  wzzkaifa  阅读(3)  评论(0)    收藏  举报