資料表常包含 `created_by`、`created_time`、`updated_by`、`updated_time` 等審計欄位。若在每個 Service 手動設值,不僅繁瑣易漏,還汙染業務邏輯。MyBatis 攔截器可在 SQL 執行前統一攔截,自動填充這些通用欄位。
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AuditFieldInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
// args[0] = MappedStatement,判斷是 INSERT/UPDATE
MappedStatement ms = (MappedStatement) args[0];
SqlCommandType type = ms.getSqlCommandType();
if (type != SqlCommandType.INSERT && type != SqlCommandType.UPDATE) {
return invocation.proceed();
}
// args[1] = 參數物件(可能是實體、Map、@Param 包裝的 Map)
Object param = args[1];
processParameter(param);
return invocation.proceed();
}
private void processParameter(Object param) {
if (param instanceof Map) {
// @Param 多參數或原始 Map
for (Object val : ((Map) param).values()) {
fillAuditFields(val);
}
} else {
// 單一實體物件
fillAuditFields(param);
}
}
private void fillAuditFields(Object entity) {
try {
// 使用 BeanUtils 靜默設值,避免無 setter 報錯
BeanUtils.setProperty(entity, "createrUsername", getCurrentUser());
BeanUtils.setProperty(entity, "createDT", new Date());
BeanUtils.setProperty(entity, "updaterUsername", getCurrentUser());
BeanUtils.setProperty(entity, "updateDT", new Date());
} catch (Exception e) {
// 忽略無對應欄位的實體
}
}
private String getCurrentUser() {
// 實際專案從 SecurityContext / ThreadLocal 獲取
return "system";
}
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override public void setProperties(Properties properties) {}
}
<!-- mybatis-config.xml 或 Spring Boot @Configuration -->
<plugins>
<plugin interceptor="com.yourpkg.AuditFieldInterceptor" />
</plugins>
| 考量點 | 處理策略 |
|---|---|
| 參數型別多樣 | 支援單一實體、Map、@Param 包裝 Map,遞迴處理 |
| 非全表需審計 | 用 `try-catch` 靜默跳過無審計欄位的實體 |
| 效能影響 | 反射設值有微小開銷,核心路徑可接受;大批量匯入建議關閉 |
| 事務一致性 | 攔截器在 Executor.update 內,同一事務內多次 update 時間一致 |
作者註記:DAO 層攔截雖可行,但離業務語境太遠(無法獲取當前登入使用者等上下文)。改用 `@Aspect` 切在 Service 層,能自然取得 `SecurityContext`、支援 SpEL 表達式、配合 `@Transactional` 語意更清晰。
MyBatis 攔截器適合「純資料層」的通用邏輯(如分頁、加密、只讀從庫路由)。涉及業務上下文的審計填充,**Spring AOP 在 Service 層才是正解**。