6 Elegant Ways to Auto‑Fill Common Fields in SpringBoot (No Hand‑Coding Needed)

SpringBoot developers can eliminate repetitive manual setting of common fields like create_time, update_time, create_by, and update_by by using six automatic filling solutions—from database default values and MyBatis‑Plus meta‑object handlers to JPA auditing, MyBatis interceptors, AOP aspects, and controller request‑body advice—each with its own pros, cons, and ideal scenarios.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
6 Elegant Ways to Auto‑Fill Common Fields in SpringBoot (No Hand‑Coding Needed)

When building backend services with SpringBoot, developers often repeat the same code to set standard columns such as create_time, update_time, create_by, and update_by. Missing a single assignment can cause data bugs in production. The SpringBoot ecosystem offers six distinct automatic‑filling techniques that remove this boiler‑plate.

1. Database column default values (zero‑code solution)

Define default values directly in the table definition. MySQL supports ON UPDATE CURRENT_TIMESTAMP to refresh the update time on each row modification.

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

Pros: no Java code, guaranteed by the database, best performance, works for batch SQL or manual DB changes.

Cons: only works for time fields, not portable to Oracle/PostgreSQL (need triggers), cannot customize logic for special cases.

Suitable 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. By annotating entity fields with @TableField(fill = FieldFill.INSERT) or @TableField(fill = FieldFill.INSERT_UPDATE), the framework automatically populates them during insert or update.

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

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;
}

Custom handler implementation:

@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, supports any field type, mature in the Chinese ecosystem, flexible custom logic.

Cons: depends on MyBatis‑Plus (cannot be used with pure MyBatis), does not trigger for raw XML SQL without entity mapping.

Suitable scenario: most projects that already use MyBatis‑Plus; it offers the best cost‑performance balance.

3. Spring Data JPA auditing

JPA provides built‑in auditing annotations. Enable it with @EnableJpaAuditing and implement AuditorAware<Long> to supply the current user.

@SpringBootApplication
@EnableJpaAuditing
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@Component
public class SpringSecurityAuditorAware implements AuditorAware<Long> {
    @Override
    public Optional<Long> getCurrentAuditor() {
        Long userId = SecurityUtils.getCurrentUserId();
        return Optional.ofNullable(userId);
    }
}
@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: native JPA support, zero extra dependencies, concise annotations cover all fields.

Cons: limited to JPA stack; cannot be used with MyBatis.

Suitable scenario: projects that use Spring Data JPA or Spring Data JDBC.

4. Native MyBatis interceptor

For legacy projects that stick to pure MyBatis, a custom Interceptor can fill fields by reflecting on the parameter object.

@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 dependencies, fully customizable logic, works for any MyBatis execution path.

Cons: requires manual reflection code, higher maintenance cost, batch and complex parameter handling need extra work.

Suitable scenario: legacy MyBatis projects that cannot adopt MyBatis‑Plus.

5. AOP aspect filling

Define a custom annotation and an aspect that intercepts service methods to set fields.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    OperationType value();
    enum OperationType { INSERT, UPDATE }
}
@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);
    }
}

Pros: works across any ORM (MyBatis, JPA, JDBC), highest flexibility, can be extended to logging or data‑permission logic.

Cons: requires adding the annotation to business methods, slight performance overhead from reflection, does not affect direct mapper calls.

Suitable scenario: projects mixing multiple ORMs or needing highly custom fill rules.

6. Controller‑layer request‑body advice

Implement RequestBodyAdvice to enrich DTOs before they reach the service layer.

@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: fills at the request entry point, completely transparent to business logic, suitable for operator, request‑time, trace‑id fields.

Cons: only works for @RequestBody parameters; internal calls, scheduled jobs, MQ consumers are not covered.

Suitable scenario: projects that want uniform handling of inbound request fields without touching the service or ORM layers.

Comparison and selection guidance

All six approaches cover the full stack from the database up to the controller. Their trade‑offs can be summarised as:

Database default values : minimal effort, only time fields, database‑specific.

MyBatis‑Plus : low effort, works for any column, requires MP dependency.

JPA auditing : zero extra libs for JPA projects, supports any column, JPA‑only.

Native MyBatis interceptor : medium effort, fully custom, pure MyBatis.

AOP aspect : high flexibility, works across ORMs, needs method annotations.

Controller advice : medium effort, fills request‑level fields, limited to HTTP bodies.

Selection recommendations:

Prefer the solution that matches the existing tech stack (MP → MP auto‑fill, JPA → JPA auditing).

For simple projects with only timestamps, use database defaults.

When custom rules or mixed frameworks are required, choose the AOP aspect.

For legacy pure MyBatis codebases, implement the custom interceptor.

Practical tips

All solutions assume a common base class ( BaseEntity or BaseDTO) that defines the shared fields.

Implement “fill only when null” logic to preserve manually set values (e.g., MP’s strictInsertFill or reflection checks).

Test batch insert/update scenarios because some approaches may only handle single objects by default.

In asynchronous contexts (MQ, scheduled jobs) the security context may be missing; provide a fallback user or pass the operator explicitly.

Avoid over‑engineering: for many projects, a combination of database defaults and one framework‑native solution covers >80% of cases.

By extracting these repetitive field‑setting concerns into the framework layer, developers can focus on business logic, improve code maintainability, and reduce the risk of data inconsistencies.

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-PlusAuto-fillJPA
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

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.