在软件开发中,数据库表常包含公共字段,如创建时间(create_time)、更新时间(update_time)、创建人(create_by)和更新人(update_by)。这些字段需要在插入或更新记录时自动填充,避免手动操作带来的冗余和错误。本文将探讨两种主流实现方式:MyBatis-Plus的元数据处理器和AOP(面向切面编程),并结合BaseEntity辅助设计,分析其优缺点,帮助您选择最适合的方案。


1. 引言:为什么需要公共字段填充?

在数据库操作中,公共字段的填充是常见需求。例如,插入新记录时,自动设置创建时间和创建人;更新记录时,自动更新修改时间和修改人。手动填充不仅繁琐,还容易遗漏或出错。自动化填充能提升代码可维护性和一致性。常见的实现方式包括:

  • MyBatis-Plus元数据处理器:MyBatis-Plus(简称MP)是MyBatis的增强工具,内置了字段自动填充功能。
  • AOP:使用Spring AOP拦截方法调用,在切面中填充字段。
  • BaseEntity辅助:定义一个基类实体,包含公共字段,其他实体类继承它,实现代码复用。

下面我们详细分析每种方法。


2. MyBatis-Plus元数据处理器

MyBatis-Plus提供了MetaObjectHandler接口,用于在插入或更新操作时自动填充字段。实现步骤如下:

  1. 创建自定义处理器:实现MetaObjectHandler接口,重写insertFillupdateFill方法。
  2. 配置字段注解:在实体类字段上使用@TableField注解,指定填充策略。
  3. 注册处理器:在Spring Boot中,将处理器注册为Bean。

优点

  • 简单易用:MP内置支持,无需额外配置,代码简洁。
  • 高效:直接在ORM层面处理,性能较高。
  • 无缝集成:与MP的其他功能(如分页、乐观锁)兼容性好。

缺点

  • 依赖性强:必须使用MyBatis-Plus,如果项目不采用MP,则无法使用。
  • 灵活性有限:填充逻辑局限于实体类操作,不能扩展到其他业务层。

代码示例

// 自定义元数据处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("createBy", "system", metaObject); // 假设当前用户为system
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
        this.setFieldValByName("updateBy", "system", metaObject);
    }
}

// BaseEntity辅助类(可选)
@Data
public abstract class BaseEntity {
    private Date createTime;
    private String createBy;
    private Date updateTime;
    private String updateBy;
}

// 实体类继承BaseEntity
public class User extends BaseEntity {
    @TableField(fill = FieldFill.INSERT)
    private Long id;
    private String name;
}

说明BaseEntity定义公共字段,实体类继承它后,MP处理器会自动填充这些字段。注解@TableField(fill = FieldFill.INSERT)指定字段只在插入时填充。


3. AOP实现

AOP(Aspect-Oriented Programming)通过切面编程,在方法执行前后注入逻辑。这里我们使用Spring AOP拦截DAO层方法,填充公共字段。实现步骤:

  1. 定义切面:创建一个切面类,使用@Aspect@Component注解。
  2. 定义切点:拦截Mapper接口的插入和更新方法。
  3. 实现通知:在方法执行前(@Before)或后(@After)填充字段。
  4. 结合BaseEntity:在切面中操作实体对象的公共字段。

优点

  • 通用性强:不依赖特定ORM框架,适用于MyBatis、JPA或其他持久层。
  • 灵活性高:可扩展到业务层,例如在Service方法中填充字段。
  • 解耦性好:填充逻辑独立于业务代码,便于维护。

缺点

  • 配置复杂:需要熟悉AOP概念,如切点表达式和通知类型。
  • 性能开销:AOP拦截可能引入额外性能损耗。
  • 错误处理难:如果切面逻辑出错,可能影响原始方法执行。

代码示例

// 切面定义
@Aspect
@Component
public class FieldFillAspect {
    // 拦截所有Mapper的insert和update方法
    @Pointcut("execution(* com.example.mapper.*.insert*(..)) || execution(* com.example.mapper.*.update*(..))")
    public void daoOperation() {}
    
    @Before("daoOperation()")
    public void fillFields(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof BaseEntity) { // 检查是否为BaseEntity子类
                BaseEntity entity = (BaseEntity) arg;
                // 根据方法名判断是插入还是更新
                String methodName = joinPoint.getSignature().getName().toLowerCase();
                if (methodName.contains("insert")) {
                    entity.setCreateTime(new Date());
                    entity.setCreateBy("system");
                } else if (methodName.contains("update")) {
                    entity.setUpdateTime(new Date());
                    entity.setUpdateBy("system");
                }
            }
        }
    }
}

// BaseEntity辅助类
@Data
public abstract class BaseEntity {
    private Date createTime;
    private String createBy;
    private Date updateTime;
    private String updateBy;
}

// 实体类继承BaseEntity
public class User extends BaseEntity {
    private Long id;
    private String name;
}

说明:切面拦截所有Mapper的插入和更新方法,检查参数是否为BaseEntity类型,然后填充字段。BaseEntity确保字段统一,减少重复代码。


4. BaseEntity的作用

无论使用MP元数据处理器还是AOP,BaseEntity都是一个有价值的辅助工具:

  • 代码复用:定义公共字段,避免在每个实体类中重复声明。
  • 统一管理:填充逻辑可以基于BaseEntity类型操作,简化处理。
  • 扩展性:可添加通用方法,如日志记录或校验。

适用场景

  • 如果实体类共享相同字段,强烈推荐使用BaseEntity
  • 结合MP或AOP时,它能让填充逻辑更集中。

5. 比较与选择建议

两种方法各有优劣,选择取决于项目需求:

  • 推荐MyBatis-Plus元数据处理器:如果项目已使用MyBatis-Plus,优先选择它。简单、高效,且与MP生态无缝集成。结合BaseEntity,代码更简洁。
    • 适用场景:中小型项目,ORM框架固定为MP。
  • 推荐AOP:如果项目不使用MP,或需要跨框架填充字段(如JPA或JDBC),AOP更灵活。它不依赖特定ORM,但需要额外配置。
    • 适用场景:大型项目,持久层多样化,或需要填充非数据库字段(如业务层日志)。

综合建议

  1. 优先使用MP元数据处理器:在MP项目中,它是最佳选择,开发效率高。
  2. AOP作为备选:当MP不适用时,AOP提供通用解决方案。
  3. 必用BaseEntity:无论哪种方法,都建议定义BaseEntity来统一字段,提升代码可维护性。

6. 结论

公共字段填充是提升代码质量的常见实践。MyBatis-Plus元数据处理器以其简单性和高效性成为首选,尤其适用于MP项目。AOP则提供了更大的灵活性,适用于复杂或多框架环境。结合BaseEntity,两者都能实现优雅的字段管理。最终选择应基于项目技术栈和需求:在标准Spring Boot + MP项目中,MP处理器更优;否则,AOP是可靠替代。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐