Free Your Hands: 6 SpringBoot Techniques for Automatic Common Field Filling

The article analyzes the repetitive maintenance of common fields like createTime and updateUser in SpringBoot CRUD services, presents six production‑tested strategies—including MyBatis‑Plus meta‑object handling, AOP with custom annotations, multi‑data‑source adaptation, distributed ID generation, caching, batch optimization, and audit logging—detailing implementations, pitfalls, and best‑practice recommendations that can cut code by 90% and boost development efficiency.

Programmer XiaoFu
Programmer XiaoFu
Programmer XiaoFu
Free Your Hands: 6 SpringBoot Techniques for Automatic Common Field Filling

When building CRUD services with SpringBoot, every entity often repeats fields such as createTime, updateTime, createUser, and updateUser. Manually setting these fields in each service method is inefficient, error‑prone, and hard to maintain.

Pain Points

High code duplication – every service method must set the same fields.

High maintenance cost – changing a field requires modifications in many places.

Easy to miss updates, especially for the update operation.

Basic Solution: MyBatis‑Plus Automatic Filling

Define a MetaObjectHandler implementation that fills fields on insert and update:

@Slf4j
@Component
public class AutoFillHandler implements MetaObjectHandler {
    @Override
    public 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());
    }
    @Override
    public 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");
    }
}

Annotate the base entity class:

@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;
}

public class Order extends BaseEntity {
    // business fields ...
}

Advanced Scheme: AOP Unified Handling

Create a custom annotation and enum to indicate the operation type:

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

public enum OperationType {
    INSERT,
    UPDATE
}

Implement an aspect that intercepts methods annotated with @AutoFill and fills the fields:

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    @Autowired
    private 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");
    }
}

Production‑Level Best Practices

Multi‑data‑source adaptation:

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MultiDataSourceAutoFillHandler();
    }
}

public class MultiDataSourceAutoFillHandler extends MetaObjectHandler {
    // Dynamically handle based on the current data source
}

Distributed ID generation (Snowflake) integrated into the auto‑fill handler:

public class SnowflakeIdGenerator {
    // implementation omitted for brevity
}

@Override
public void insertFill(MetaObject metaObject) {
    this.strictInsertFill(metaObject, "id", String.class, idGenerator.nextId());
    // other fields as before
}

Common Pitfalls and Guardrails

Null‑pointer protection when retrieving the current user:

private String safeGetUser() {
    return Optional.ofNullable(SecurityContextHolder.getContext())
        .map(SecurityContext::getAuthentication)
        .map(Authentication::getPrincipal)
        .map(principal -> {
            if (principal instanceof UserDetails) {
                return ((UserDetails) principal).getUsername();
            }
            return principal.toString();
        })
        .orElse("system");
}

Prevent field overwrite on update by configuring @TableField strategy:

@TableField(fill = FieldFill.INSERT, updateStrategy = FieldStrategy.NEVER)
private String createUser;

Performance Optimizations

Cache the current user in a ThreadLocal to avoid repeated header reads:

public class UserContextHolder {
    private static final ThreadLocal<String> userHolder = new ThreadLocal<>();
    public static void setUser(String user) { userHolder.set(user); }
    public static String getUser() { return userHolder.get(); }
    public static void clear() { userHolder.remove(); }
}

public class UserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        UserContextHolder.setUser(request.getHeader("X-User-Id"));
        return true;
    }
}

Batch insert optimization by pre‑computing common fields:

@Transactional
public void batchInsert(List<Order> orders) {
    String user = getCurrentUser();
    LocalDateTime now = LocalDateTime.now();
    orders.forEach(order -> {
        order.setCreateTime(now);
        order.setCreateUser(user);
        order.setUpdateTime(now);
        order.setUpdateUser(user);
    });
    orderMapper.batchInsert(orders);
}

Monitoring and Auditing

JPA auditing annotations on the base entity:

@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
    @CreatedBy
    private String createUser;
    @LastModifiedBy
    private String updateUser;
    @CreatedDate
    private LocalDateTime createTime;
    @LastModifiedDate
    private LocalDateTime updateTime;
}

Operation log aspect that records who performed which operation:

@Aspect
@Component
public class OperationLogAspect {
    @AfterReturning("@annotation(autoFill)")
    public void logOperation(AutoFill autoFill) {
        LogEntry log = new LogEntry();
        log.setOperator(getCurrentUser());
        log.setOperationType(autoFill.value().name());
        logService.save(log);
    }
}

Conclusion and Future Outlook

By combining the six strategies in production, the team achieved a 90% reduction in code related to common field maintenance, a 75% drop in related bugs, and a 40% increase in new‑feature development speed. Looking ahead, the evolution of Spring Data JPA and its integration with reactive programming may enable fully non‑blocking automatic field filling across the entire request chain.

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.

backendAOPSpringBootMyBatis-PlusAutomatic Field Filling
Programmer XiaoFu
Written by

Programmer XiaoFu

xiaofucode.com – a programmer learning guide driven by the pursuit of profit

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.