6 Ways to Auto‑Fill Common Fields in SpringBoot and Stop Writing Boilerplate Code

This article reviews six practical approaches—from database column defaults to AOP interceptors—for automatically populating common fields such as timestamps, creator, and tenant IDs in SpringBoot applications, compares their pros, cons, and applicability, and offers guidance on selecting the most suitable solution.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
6 Ways to Auto‑Fill Common Fields in SpringBoot and Stop Writing Boilerplate Code

1. Database column default values

Use the database itself to fill timestamp fields. MySQL supports ON UPDATE CURRENT_TIMESTAMP to refresh the update time automatically.

Implementation

CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(64) NOT NULL,
    -- create_time: automatically filled on insert
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    -- update_time: automatically refreshed on insert and update
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '用户表';

Pros

Zero Java code; the database guarantees the values.

Best performance, no runtime overhead.

Works for bulk SQL or manual DB changes.

Cons

Only timestamp fields can be filled; cannot handle creator, tenant ID, etc.

SQL syntax is not portable (MySQL only; other DBs need triggers).

Cannot flexibly control filling logic for special cases.

Applicable scenario: Simple projects that only need time‑type fields and want the lowest implementation cost.

2. MyBatis‑Plus automatic filling

MyBatis‑Plus provides the MetaObjectHandler extension point to automatically fill fields on insert and update.

Step 1 – Mark fill timing in the base entity

@Data
public class BaseEntity {
    /** creation time */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /** update time */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /** creator ID */
    @TableField(fill = FieldFill.INSERT)
    private Long createBy;

    /** updater ID */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;
}

All business entities extend this base class.

Step 2 – Implement the filling handler

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
        Long userId = SecurityUtils.getCurrentUserId();
        this.strictInsertFill(metaObject, "createBy", () -> userId, Long.class);
        this.strictInsertFill(metaObject, "updateBy", () -> userId, Long.class);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
        Long userId = SecurityUtils.getCurrentUserId();
        this.strictUpdateFill(metaObject, "updateBy", () -> userId, Long.class);
    }
}

Pros

Annotation‑driven, low intrusion.

Supports both insert and update, and any field type (creator, tenant, etc.).

Widely used in the Chinese ecosystem; works out‑of‑the‑box for most MP projects.

Flexible custom logic for special scenarios.

Cons

Depends on MyBatis‑Plus; not usable in pure MyBatis projects.

Direct execution of native XML SQL bypasses the filling logic.

Applicable scenario: The majority of business systems that already use MyBatis‑Plus.

3. Spring Data JPA auditing

Spring Data JPA offers built‑in auditing with four annotations to fill creation time, update time, creator, and updater.

Step 1 – Enable auditing

@SpringBootApplication
@EnableJpaAuditing
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Step 2 – Provide current auditor

@Component
public class SpringSecurityAuditorAware implements AuditorAware<Long> {
    @Override
    public Optional<Long> getCurrentAuditor() {
        Long userId = SecurityUtils.getCurrentUserId();
        return Optional.ofNullable(userId);
    }
}

Step 3 – Annotate the base entity

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createTime;

    @LastModifiedDate
    private LocalDateTime updateTime;

    @CreatedBy
    @Column(updatable = false)
    private Long createBy;

    @LastModifiedBy
    private Long updateBy;
}

Pros

Official JPA support, zero extra dependencies.

Only four annotations needed; minimal code.

Clear semantics for both insert and update.

Cons

Limited to the JPA stack; cannot be used with MyBatis.

Applicable scenario: Projects that use Spring Data JPA or Spring Data JDBC.

4. Native MyBatis interceptor

For legacy projects that use plain MyBatis and do not want to introduce MP, a custom interceptor can fill fields by reflecting on the parameter object.

Full implementation

@Component
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class AutoFillInterceptor implements Interceptor {
    private static final String CREATE_TIME = "createTime";
    private static final String UPDATE_TIME = "updateTime";
    private static final String CREATE_BY = "createBy";
    private static final String UPDATE_BY = "updateBy";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object param = invocation.getArgs()[1];
        SqlCommandType commandType = ms.getSqlCommandType();
        try {
            Long userId = SecurityUtils.getCurrentUserId();
            LocalDateTime now = LocalDateTime.now();
            if (SqlCommandType.INSERT.equals(commandType)) {
                setFieldValue(param, CREATE_TIME, now);
                setFieldValue(param, UPDATE_TIME, now);
                setFieldValue(param, CREATE_BY, userId);
                setFieldValue(param, UPDATE_BY, userId);
            } else if (SqlCommandType.UPDATE.equals(commandType)) {
                setFieldValue(param, UPDATE_TIME, now);
                setFieldValue(param, UPDATE_BY, userId);
            }
        } catch (Exception e) {
            log.warn("公共字段自动填充失败,跳过", e);
        }
        return invocation.proceed();
    }

    private void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        if (obj instanceof Map) {
            ((Map<String, Object>) obj).put(fieldName, value);
            return;
        }
        Field field = ReflectUtil.getField(obj.getClass(), fieldName);
        if (field != null) {
            field.setAccessible(true);
            if (field.get(obj) == null) {
                field.set(obj, value);
            }
        }
    }
}

Pros

No third‑party extensions; pure MyBatis.

Highly flexible; filling rules are fully custom.

Works with all MyBatis execution paths.

Cons

Requires writing reflection and interceptor code.

Batch operations and complex nested parameters need extra handling.

Higher maintenance cost than framework‑native solutions.

Applicable scenario: Legacy MyBatis projects that cannot adopt MyBatis‑Plus.

5. AOP aspect filling

When the technology stack is mixed or you prefer not to touch the ORM layer, an AOP aspect can fill fields in the service layer.

Step 1 – Define a custom annotation

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

Step 2 – Implement the aspect

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    @Around("@annotation(autoFill)")
    public Object around(ProceedingJoinPoint joinPoint, AutoFill autoFill) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (args.length == 0) return joinPoint.proceed();
        for (Object arg : args) {
            if (arg == null) continue;
            try {
                fillFields(arg, autoFill.value());
            } catch (Exception e) {
                log.warn("AOP自动填充字段失败", e);
            }
        }
        return joinPoint.proceed();
    }

    private void fillFields(Object obj, AutoFill.OperationType type) {
        LocalDateTime now = LocalDateTime.now();
        Long userId = SecurityUtils.getCurrentUserId();
        if (type == AutoFill.OperationType.INSERT) {
            ReflectUtil.setFieldValue(obj, "createTime", now);
            ReflectUtil.setFieldValue(obj, "createBy", userId);
        }
        ReflectUtil.setFieldValue(obj, "updateTime", now);
        ReflectUtil.setFieldValue(obj, "updateBy", userId);
    }
}

Step 3 – Annotate business methods

@Service
public class UserService {
    @AutoFill(AutoFill.OperationType.INSERT)
    public void addUser(SysUser user) {
        userMapper.insert(user);
    }

    @AutoFill(AutoFill.OperationType.UPDATE)
    public void updateUser(SysUser user) {
        userMapper.updateById(user);
    }
}

Pros

Works with any ORM (MyBatis, JPA, JDBC).

Filling timing and rules are fully customizable.

Can be extended to add logging, data‑permission, etc.

Cons

Requires adding annotations to business methods.

Direct mapper calls that bypass the service layer are not affected.

Reflection introduces a minor performance overhead.

Applicable scenario: Projects with mixed ORM frameworks or highly custom filling requirements.

6. Controller‑layer request body enhancement

Fill common fields as soon as the request reaches the controller, suitable for fields like operator, request time, or trace ID.

Implementation with RequestBodyAdvice

@RestControllerAdvice
@Slf4j
public class GlobalRequestBodyAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
                         Class<? extends HttpMessageConverter<?>> converterType) {
        try {
            Class<?> clazz = Class.forName(targetType.getTypeName());
            return BaseDTO.class.isAssignableFrom(clazz);
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                              MethodParameter parameter, Type targetType,
                              Class<? extends HttpMessageConverter<?>> converterType) {
        if (body instanceof BaseDTO baseDTO) {
            baseDTO.setOperatorId(SecurityUtils.getCurrentUserId());
            baseDTO.setRequestTime(LocalDateTime.now());
            baseDTO.setTraceId(TraceIdUtil.getTraceId());
        }
        return body;
    }
    // other methods omitted
}

Pros

Unified handling at the request entry point; business layer is unaware.

Ideal for filling request‑related fields (operator, client info, trace ID).

Does not interfere with ORM logic.

Cons

Only works for @RequestBody parameters; form submissions, URL params need separate handling.

Does not affect internal calls, scheduled tasks, or MQ consumption.

Update‑scenario field filling is less suitable.

Applicable scenario: Projects that want a uniform solution for API input fields without touching service or persistence layers.

Selection Advice

Prefer the native solution of your stack: use MP auto‑fill for MyBatis‑Plus projects, JPA auditing for JPA projects; native mechanisms have the lowest maintenance cost.

Simple scenarios: if only timestamp fields are needed, database defaults are the most lightweight choice.

Custom requirements: when you need complex rules or mix multiple ORMs, AOP aspects provide the highest flexibility.

Legacy migration: for pure MyBatis codebases that cannot adopt MP, a custom interceptor is the most appropriate.

Notes

1. Unified base class is prerequisite

All solutions assume that common fields are extracted into a BaseEntity or BaseDTO superclass, which all business classes inherit.

2. Preserve manual overrides

Filling logic should be "empty‑value only"; if a field already has a value, do not overwrite it. MP’s strictInsertFill and the reflection checks in the interceptor follow this principle.

3. Pay attention to batch operations

Many approaches (especially custom interceptors and AOP) may not handle batch inserts/updates out of the box. Test batch scenarios thoroughly to avoid missing fills.

4. Async contexts need explicit user information

When filling creator/updater in asynchronous tasks, MQ consumers, or scheduled jobs, the login context may be absent; either pass the operator explicitly or provide a default value.

5. Avoid over‑engineering

For straightforward projects, combining database defaults with occasional manual settings solves the majority of cases. Choose the simplest solution that meets the requirements.

Conclusion

Automatic filling of common fields is a recurring pain point in backend development. The six methods presented cover the full stack—from the database layer to the controller layer—each with distinct trade‑offs. Selecting the approach that aligns with your technology stack and maintenance budget eliminates repetitive boiler‑plate code and lets developers focus on real business logic.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AOPSpringBootMyBatis-PlusJPARequestBodyAdviceautomatic field fillingMyBatis interceptor
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.