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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
