# How Automatic Audit Field Population Works in ContiNew Admin: MyBatis-Plus MetaObjectHandler Explained

> Discover how ContiNew Admin uses MyBatis-Plus MetaObjectHandler to automatically populate user and timestamp fields on insert. Simplify your code with this powerful feature.

- Repository: [OpenContiNew/continew-admin](https://github.com/continew-org/continew-admin)
- Tags: internals
- Published: 2026-02-27

---

**ContiNew Admin leverages MyBatis-Plus MetaObjectHandler to automatically populate `createUser` and `createTime` fields during database inserts by retrieving the current authenticated user from Sa-Token sessions and applying timestamps without requiring manual service-layer code.**

ContiNew Admin eliminates boilerplate audit logging through a sophisticated automatic audit field population mechanism built on MyBatis-Plus infrastructure. This open-source Spring Boot admin system automatically injects creation metadata into every persistent entity without developers writing explicit assignment code in service layers. Understanding this architecture requires examining four integrated components that handle entity definition, handler registration, value resolution, and user context retrieval.

## The Four-Component Architecture of Automatic Audit Field Population

### Entity Foundation with BaseDO

All database entities extend `BaseDO`, which defines the standard audit columns and instructs MyBatis-Plus when to fill them. Located at [`continew-common/src/main/java/top/continew/admin/common/base/model/entity/BaseDO.java`](https://github.com/continew-org/continew-admin/blob/main/continew-common/src/main/java/top/continew/admin/common/base/model/entity/BaseDO.java), this base class declares `createUser`, `createTime`, `updateUser`, and `updateTime` fields annotated with `@TableField(fill = FieldFill.INSERT)` for creation fields and `FieldFill.INSERT_UPDATE` for modification tracking. These annotations signal MyBatis-Plus to invoke the registered `MetaObjectHandler` whenever an insert or update operation occurs, ensuring the framework knows which fields require external population.

### Handler Registration via MybatisPlusConfiguration

The system activates automatic filling through Spring bean registration in [`continew-common/src/main/java/top/continew/admin/common/config/mybatis/MybatisPlusConfiguration.java`](https://github.com/continew-org/continew-admin/blob/main/continew-common/src/main/java/top/continew/admin/common/config/mybatis/MybatisPlusConfiguration.java). This configuration class exposes a `MetaObjectHandler` bean that returns an instance of `MyBatisPlusMetaObjectHandler`, which MyBatis-Plus automatically detects and applies to all SQL operations. By registering this handler as a Spring-managed component, ContiNew Admin ensures every MyBatis-Plus insert or update triggers the audit population logic without explicit mapper configuration or XML modifications.

### Insert-Fill Logic in MyBatisPlusMetaObjectHandler

The core implementation resides in [`continew-common/src/main/java/top/continew/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java`](https://github.com/continew-org/continew-admin/blob/main/continew-common/src/main/java/top/continew/admin/common/config/mybatis/MyBatisPlusMetaObjectHandler.java), specifically within the `insertFill` method. When MyBatis-Plus executes an insert, this handler obtains the current user ID via `UserContextHolder.getUserId()` and captures the current moment using `LocalDateTime.now()`. For entities extending `BaseDO`, the handler invokes concrete setters `setCreateUser` and `setCreateTime` directly. For entities that do not inherit from `BaseDO`, the generic `fillFieldValue` method employs reflection to locate and populate fields named exactly `createUser` and `createTime`, providing flexibility for legacy or specialized entities while maintaining the automatic audit field population contract.

### User Context Resolution via UserContextHolder

Authentication context flows through [`continew-common/src/main/java/top/continew/admin/common/context/UserContextHolder.java`](https://github.com/continew-org/continew-admin/blob/main/continew-common/src/main/java/top/continew/admin/common/context/UserContextHolder.java), which maintains the current user in a `TransmittableThreadLocal` to support thread-safe access across asynchronous boundaries. The static `getUserId()` method extracts the user identifier from the active Sa-Token session via `StpUtil.getSession().getModel`, returning the primary key of the authenticated principal. This architecture decouples the audit mechanism from HTTP request handling, allowing the automatic audit field population to function consistently whether triggered by web controllers, scheduled jobs, or message consumers.

## The Complete Insert Flow: From Service Call to Persisted Audit Data

When a service layer invokes `userMapper.insert(entity)`, the automatic audit field population executes through this sequence:

1. **Entity Preparation**: The application constructs the entity object, typically via MapStruct conversion from a DTO, leaving audit fields null.
2. **MyBatis-Plus Interception**: The framework detects the `@TableField(fill = FieldFill.INSERT)` annotations and delegates to the registered `MyBatisPlusMetaObjectHandler`.
3. **Value Resolution**: The handler calls `insertFill`, which retrieves `LocalDateTime.now()` for temporal data and invokes `UserContextHolder.getUserId()` to resolve the creator's identity from the Sa-Token session stored in thread-local storage.
4. **Field Population**: For `BaseDO` subclasses, the handler uses type-safe setters; for other entities, it uses reflection via `fillFieldValue` to inject values into `createUser` and `createTime` fields.
5. **SQL Execution**: MyBatis-Plus generates and executes the INSERT statement with the now-populated audit columns, persisting the record with complete provenance metadata.

This seamless integration ensures that `createUser` contains the current user's ID and `createTime` stores the precise `LocalDateTime` of insertion without explicit assignment in business logic.

## Practical Implementation Example

The following example demonstrates automatic audit field population in action when creating a new user entity:

```java
// Entity extends BaseDO, inheriting createUser and createTime fields
UserDO user = new UserDO();
user.setUsername("alice");
user.setPassword(passwordEncoder.encode("secret"));
user.setEmail("alice@example.com");

// Insert operation triggers automatic audit field population
userMapper.insert(user);

// After insertion, audit fields are automatically populated:
// user.getCreateUser() returns current authenticated user ID
// user.getCreateTime() returns LocalDateTime of insertion
System.out.println("Created by: " + user.getCreateUser());
System.out.println("Created at: " + user.getCreateTime());

```

No manual handling of `createUser` or `createTime` appears in the service layer. The `userMapper.insert()` call transparently triggers `MyBatisPlusMetaObjectHandler.insertFill`, which pulls the authenticated principal from `UserContextHolder` and applies the timestamp, demonstrating the zero-boilerplate advantage of this architecture.

## Summary

- **BaseDO establishes the contract**: All entities inherit audit fields annotated with `@TableField(fill = FieldFill.INSERT)`, signaling MyBatis-Plus to invoke automatic population.
- **Spring bean registration activates the handler**: `MybatisPlusConfiguration` exposes `MyBatisPlusMetaObjectHandler` as a `MetaObjectHandler` bean, enabling framework-level interception.
- **Dual population strategy**: The handler uses direct setters for `BaseDO` subclasses and reflection-based `fillFieldValue` for other entities, ensuring flexibility.
- **Sa-Token integration**: `UserContextHolder` provides thread-safe user ID retrieval from `StpUtil` sessions, making audit fields user-aware without coupling to web layers.
- **Zero service-layer code**: Developers call standard MyBatis-Plus `insert()` methods while the framework handles `createUser` and `createTime` assignment automatically.

## Frequently Asked Questions

### Does automatic audit field population work for batch insert operations?

Yes. MyBatis-Plus invokes `insertFill` for each entity in a batch operation individually. The `MyBatisPlusMetaObjectHandler` processes every object in the collection, calling `UserContextHolder.getUserId()` and `LocalDateTime.now()` for each record, ensuring consistent audit metadata across bulk inserts without requiring explicit loop handling in service code.

### What happens if no user is authenticated when an entity is inserted?

If `UserContextHolder.getUserId()` cannot resolve a user from the current Sa-Token session (returning null), the handler still populates `createTime` with the current timestamp but sets `createUser` to null. For system operations or scheduled tasks running without authentication, developers should explicitly set the `createUser` field before insertion or extend the handler logic to apply a system user default.

### Can I customize the field names for audit tracking?

Yes. While `BaseDO` uses standard `createUser` and `createTime` names, the `fillFieldValue` method in `MyBatisPlusMetaObjectHandler` supports arbitrary entities through reflection. To use custom field names, either extend `BaseDO` and override the setter methods, or implement a custom `MetaObjectHandler` that looks for your specific column names instead of the defaults.

### How does the audit mechanism handle update operations?

The same handler manages updates through the `updateFill` method. Fields marked with `@TableField(fill = FieldFill.INSERT_UPDATE)` or `FieldFill.UPDATE` trigger population of `updateUser` and `updateTime` using the same `UserContextHolder.getUserId()` and `LocalDateTime.now()` pattern, maintaining modification history alongside creation metadata.