SpringBoot 公共字段自动填充
在某些业务场景中,重复字段如create_time、update_time等频繁出现在每个实体类中。这种重复不仅增加了代码的冗余度,还导致了维护成本的上升。每次编写服务方法时,都需要手动设置这些字段,既费时又容易出错。特别是当字段变更时,需要在多个地方进行同步修改,增加了出错的风险。而且在更新操作中,若遗漏这些字段,可能会导致数据的不一致,给系统带来潜在的风险。
本文将分享一套经过生产验证的自动化方案,涵盖MyBatis-Plus、AOP等核心策略,助你彻底摆脱公共字段维护的烦恼。
方式一:MyBatis-Plus自动填充
- 配置元对象处理器
@Slf4j
@Component
public class AutoFillHandler implements MetaObjectHandler {// 插入时自动填充@Overridepublic void insertFill(MetaObject metaObject){this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());this.strictInsertFill(metaObject, "createUser", String.class, getCurrentUser());this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());this.strictUpdateFill(metaObject, "updateUser", String.class, getCurrentUser());}// 更新时自动填充@Overridepublic void updateFill(MetaObject metaObject){this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());this.strictUpdateFill(metaObject, "updateUser", String.class, getCurrentUser());}// 获取当前用户(从安全上下文)private String getCurrentUser(){
// return Optional.ofNullable(SecurityContextHolder.getContext())
// .map(SecurityContext::getAuthentication)
// .map(Authentication::getName)
// .orElse("system");return "admin";}
}
- 实体类注解配置
@Data
public 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;
}
方式二:AOP统一处理
- 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoFill {OperationType value();
}
- 切面实现
@Aspect
@Component
@Slf4j
public class AutoFillAspect{@Autowiredprivate ObjectMapper objectMapper;@Around("@annotation(autoFill)")public Object around(ProceedingJoinPoint pjp, AutoFill autoFill)throws Throwable {Object[] args = pjp.getArgs();for (Object arg : args) {if (arg instanceof BaseEntity) {fillFields((BaseEntity) arg, autoFill.value());}}return pjp.proceed(args);}private void fillFields(BaseEntity entity, OperationType type){String currentUser = getCurrentUser();LocalDateTime now = LocalDateTime.now();if (type == OperationType.INSERT) {entity.setCreateTime(now);entity.setCreateUser(currentUser);}entity.setUpdateTime(now);entity.setUpdateUser(currentUser);}// 获取当前用户(支持多线程环境)private String getCurrentUser(){return Optional.ofNullable(RequestContextHolder.getRequestAttributes()).map(attrs -> (ServletRequestAttributes) attrs).map(ServletRequestAttributes::getRequest).map(req -> req.getHeader("X-User-Id")).orElse("system");}
}
业务实现
- 准备SQL
CREATE TABLE t_order (id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID',user_id BIGINT NOT NULL COMMENT '用户ID',order_number VARCHAR(50) NOT NULL COMMENT '订单编号',order_date DATETIME NOT NULL COMMENT '订单日期',total_amount DECIMAL(10, 2) NOT NULL COMMENT '订单总金额',status VARCHAR(20) NOT NULL COMMENT '订单状态',shipping_address VARCHAR(255) NOT NULL COMMENT '收货地址',payment_method VARCHAR(50) NOT NULL COMMENT '支付方式',shipping_date DATETIME COMMENT '发货日期',delivery_date DATETIME COMMENT '送达日期',create_time DATETIME NOT NULL COMMENT '创建时间',update_time DATETIME NOT NULL COMMENT '更新时间',create_user VARCHAR(50) NOT NULL COMMENT '创建用户',update_user VARCHAR(50) NOT NULL COMMENT '更新用户',PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
- 订单实体类
@Data
@TableName("t_order")
public class Order extends BaseEntity{@TableId(type = IdType.ASSIGN_ID)private Long id; // 订单IDprivate Long userId; // 用户IDprivate String orderNumber; // 订单编号@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime orderDate; // 订单日期private BigDecimal totalAmount; // 订单总金额private String status; // 订单状态private String shippingAddress; // 收货地址private String paymentMethod; // 支付方式@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime shippingDate; // 发货日期@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime deliveryDate; // 送达日期
}
- OrderMapper
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
- IOrderService
@Service
public class IOrderService extends ServiceImpl<OrderMapper, Order> {@Resourceprivate OrderMapper orderMapper;public void insert(Order order) {orderMapper.insert(order);}public void update(Order order) {orderMapper.updateById(order);}
}
- OrderController
@RestController
@RequestMapping("order")
public class OrderController {@Autowiredprivate IOrderService orderService;@ApiOperation("插入订单")@PostMapping("/insert")// 使用AOP填充时用@AutoFill注解// @AutoFill(OperationType.INSERT)public ResponseEntity<String> insert(@RequestBody Order order){orderService.insert(order);return ResponseEntity.ok("success");}@ApiOperation("修改订单")@PostMapping("/update")// 使用AOP填充时用@AutoFill注解// @AutoFill(OperationType.UPDATE)public ResponseEntity<String> update(@RequestBody Order order){orderService.update(order);return ResponseEntity.ok("success");}
}
操作日志
- 准备SQL
CREATE TABLE `t_log` (`id` BIGINT NOT NULL COMMENT '主键ID',`operator` VARCHAR(255) NOT NULL COMMENT '操作人',`operation_type` VARCHAR(255) NOT NULL COMMENT '操作类型',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='日志表';
- 定义切面
@Aspect
@Component
public class OperationLogAspect{@Autowiredprivate LogService logService;@AfterReturning("@annotation(autoFill)")public void logOperation(AutoFill autoFill){LogEntry log = new LogEntry();log.setOperator("admin");log.setOperationType(autoFill.value().name());logService.save(log);}
}
这样,在新增或修改订单时,都会记录当前操作日志。