MyBatis攔截器終極指南:從原理到企業級實戰

在本篇文章中,我們將深入了解如何編寫一個 MyBatis 攔截器,并通過一個示例來展示如何在執行數據庫操作(如插入或更新)時,自動填充某些字段(例如 createdByupdatedBy)信息。本文將詳細講解攔截器的工作原理、代碼示例及其在 MyBatis 項目中的應用。

一、為什么需要數據庫操作攔截器?

在典型的企業級應用開發中,我們經常會遇到這樣的需求:

  1. 需要自動記錄數據行的創建時間和修改時間

  2. 需要跟蹤記錄數據操作人

  3. 需要實現全局的軟刪除邏輯

  4. 需要對敏感數據進行自動加密

  5. 需要實現SQL執行監控和慢查詢統計

傳統做法是在每個Mapper方法中手動添加這些邏輯,但這樣會導致大量重復代碼。MyBatis攔截器(Interceptor)正是為了解決這類問題而生,它可以在SQL執行的各個階段插入自定義邏輯,實現橫切關注點的統一管理。

二、MyBatis攔截器核心原理

如果對于MyBatis核心組件功能還不了解的小伙伴們建議先跳到最后一章節:第五節去了解MyBatis核心組件功能再倒回來繼續學習!

2.1 攔截器架構

MyBatis采用責任鏈模式實現攔截器機制,主要攔截點包括:

攔截接口攔截時機典型應用場景
ExecutorSQL執行前后(增刪改查操作)事務管理、分頁處理
StatementHandlerSQL語句構建時SQL改寫、危險操作攔截
ParameterHandler參數處理時參數加密、參數校驗
ResultSetHandler結果集處理時結果解密、數據脫敏

2.2 攔截器生命周期

MyBatis 中的攔截器生命周期較為簡單。它的生命周期由 Plugin.wrap() 方法控制,首先會創建代理對象并包裝目標對象。攔截器的 intercept 方法在執行目標方法前被調用,攔截器的 plugin 方法用于對目標方法的代理,setProperties 方法用于注入攔截器所需的配置屬性。

攔截目標方法調用的順序:當攔截器裝載到 MyBatis 配置中后,每次執行目標方法(如數據庫操作的 insertupdate)時,都會經過攔截器鏈。如果多個攔截器存在,它們會按照配置順序依次執行。可以通過實現Ordered接口或使用@Order注解控制執行順序:

@Intercepts(...)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FirstInterceptor implements Interceptor {}@Intercepts(...)
@Order(Ordered.LOWEST_PRECEDENCE)
public class LastInterceptor implements Interceptor {}
關鍵接口解析
  1. 初始化階段:通過@Intercepts注解聲明攔截目標

  2. 代理階段:通過plugin()方法創建代理對象

  3. 執行階段:intercept()方法處理攔截邏輯

  4. 配置階段:通過XML或Java Config注冊攔截器

// 典型攔截器聲明
@Intercepts({@Signature(type = Executor.class, method = "update",args = {MappedStatement.class, Object.class})
})
public class CustomInterceptor implements Interceptor {// 實現方法...
}

2.3 代理模式與責任鏈模式

  • 代理模式(Proxy Pattern):MyBatis 攔截器本質上是通過代理模式對目標對象進行包裝,通過 Plugin.wrap() 方法將攔截器包裝在目標對象上,形成一個代理對象。這個代理對象會攔截對目標方法的調用,執行預定的增強邏輯,再將控制權交給目標方法。
  • 責任鏈模式(Chain of Responsibility Pattern):MyBatis 的攔截器是按順序進行鏈式調用的,多個攔截器會組成一個鏈,按照聲明順序依次執行,直到所有攔截器都被調用。

三、實戰:實現自動化字段填充

3.1 需求分析

實現以下字段的自動填充:

字段名插入時自動填充更新時自動填充數據類型
created_by???String
created_time???Date
updated_by???String
updated_time???Date
is_deleted??(默認0)?Integer

?3.2 完整實現代碼解析

package com.wanren.subject.application.interceptor;import com.wanren.subject.common.util.LoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.Executor;/***@ClassName MybatisInterceptor*@Description 填充createBy,createTime等公共字段的攔截器*@Author 彭于晏*Date 2025/2/14 0:31*Version 1.0**/
@Component
@Slf4j
//@Signature指明該攔截器需要攔截哪一個接口的哪一個方法,包括如下
// 接口類型:Executor(攔截執行器方法,負責調用StatementHandler操作數據庫)
// StatementHandler(//攔截SQL語法構建處理,直接在數據庫執行SQL腳本的對象)、
// ParameterHandler(//攔截參數處理)和ResultSetHandler(//攔截結果集處理,ResultSet結果集對象轉換成List類型的集合)
//對應接口中的某一個方法的參數,比如Executor中query方法因為重載原因,有多個,args就是指明參數類型,從而確定是具體哪一個方法。
@Intercepts(@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class
}))
public class MybatisInterceptor implements Interceptor {//當被攔截的數據庫操作(如查詢、插入、更新)發生時,intercept 方法會被調用。//invocation的三個方法:Object target = invocation.getTarget();//被代理對象//Method method = invocation.getMethod();//代理方法//Object[] args = invocation.getArgs();//方法參數@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];//MyBatis 中封裝 SQL 語句信息的類,表示一個 SQL 映射。MappedStatement 對象包含 SQL 執行所需的所有信息,比如 SQL 命令類型、SQL 語句、參數類型等SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();Object parameter = invocation.getArgs()[1];//parameter 是一個 Object 類型,通常是實體類對象if(parameter == null){//執行目標方法(invocation.proceed())return invocation.proceed();}//獲取當前登錄用戶的idString loginId = LoginUtil.getLoginId();if(StringUtils.isBlank(loginId)){return invocation.proceed();}if(sqlCommandType.INSERT == sqlCommandType || sqlCommandType.UPDATE == sqlCommandType){replaceEntityProperty(parameter,loginId,sqlCommandType);}return invocation.proceed();}private void replaceEntityProperty(Object parameter, String loginId, SqlCommandType sqlCommandType) {if(parameter instanceof Map){replaceMap((Map) parameter,loginId,sqlCommandType);}else{replace(parameter,loginId,sqlCommandType);}}private void replace(Object parameter, String loginId, SqlCommandType sqlCommandType) {if(SqlCommandType.INSERT == sqlCommandType){dealInsert(parameter,loginId);}else{dealUpdate(parameter,loginId);}}private void dealUpdate(Object parameter, String loginId) {Field[] fields = getAllFields(parameter);for (Field field : fields) {try {field.setAccessible(true);//parameter是字段名,field是字段的值Object o = field.get(parameter);//如果該字段不為null,則跳過if (Objects.nonNull(o)) {field.setAccessible(false);continue;}if ("updateBy".equals(field.getName())) {field.set(parameter, loginId);field.setAccessible(false);} else if ("updateTime".equals(field.getName())) {field.set(parameter, new Date());field.setAccessible(false);} else {field.setAccessible(false);}}catch (Exception e){log.error("dealUpdate.error:{}", e.getMessage(), e);}}}private void dealInsert(Object parameter, String loginId) {Field[] fields = getAllFields(parameter);for(Field field : fields){try{//Java 語言的私有字段是不可訪問的。如果你想訪問私有字段(即在類外部訪問 private 字段),// 你必須調用 setAccessible(true) 來打破 Java 的訪問控制,允許訪問這些字段。field.setAccessible(true);Object o = field.get(parameter);if (Objects.nonNull(o)) {field.setAccessible(false);continue;}if("isDeleted".equals(field.getName())){field.set(parameter,0);field.setAccessible(false);}else if ("createdBy".equals(field.getName())) {field.set(parameter, loginId);field.setAccessible(false);} else if ("createdTime".equals(field.getName())) {field.set(parameter, new Date());field.setAccessible(false);}else {field.setAccessible(false);}}catch (Exception e){log.error("dealInsert.error:{}", e.getMessage(), e);}}}private Field[] getAllFields(Object parameter) {Class<?> clazz = parameter.getClass();List<Field> fieldList = new ArrayList<>();while (clazz != null){fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));clazz = clazz.getSuperclass();}Field[] fields =new Field[fieldList.size()];fieldList.toArray(fields);return fields;}private void replaceMap(Map parameter, String loginId, SqlCommandType sqlCommandType) {for(Object val : parameter.values()){replace(val,loginId,sqlCommandType);}}//插件用于封裝目標對象,通過這個方法可以返回目標對象本身,也可以返回一個他的代理,決定//是否要進行攔截,從而進一步決定要返回一個怎樣的對象@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}//如果我們攔截器需要用到一些變量參數,而且這個參數是支持可配置的,類似Spring中的@Value("${}")從application.properties文件獲取自定義變量屬性,這個時候我們就可以使用這個方法。//在攔截器插件的setProperties方法中進行。這些自定義屬性參數會在項目啟動的時候被加載。@Overridepublic void setProperties(Properties properties) {}
}

四、擴展應用場景

4.1 敏感數據加密

public Object intercept(Invocation invocation) {Object parameter = invocation.getArgs()[1];if (parameter instanceof User) {User user = (User) parameter;user.setPassword(encrypt(user.getPassword()));}return invocation.proceed();
}

4.2 SQL執行監控?

public Object intercept(Invocation invocation) {long start = System.nanoTime();try {return invocation.proceed();} finally {long cost = (System.nanoTime() - start)/1000000;if(cost > 1000){log.warn("慢查詢警告: {}ms - {}", cost, getSql(invocation));}Metrics.counter("sql.total.count").increment();Metrics.timer("sql.execute.time").record(cost, MILLISECONDS);}
}

4.3 多租戶數據隔離

public void replaceEntityProperty(Object parameter, String tenantId) {if (parameter instanceof TenantAware) {((TenantAware) parameter).setTenantId(tenantId);}
}

?五、MyBatis核心組件功能詳解

從MyBatis代碼實現的角度來看,MyBatis的核心組成部分涉及到多個組件和接口,它們共同協作完成了數據庫操作的管理、映射及優化等工作。以下是MyBatis的核心部件及其功能的詳細介紹:

1. Configuration(配置對象)

功能概述
Configuration?是 MyBatis 的全局配置中心,負責管理所有配置信息,包括數據庫連接、映射器、類型處理器、插件等。它是 MyBatis 初始化的核心類。

核心職責

  • 加載并解析 MyBatis 配置文件(如?mybatis-config.xml)。

  • 管理 Mapper 文件和接口的配置信息。

  • 注冊類型處理器(TypeHandler)和對象工廠(ObjectFactory)。

  • 維護攔截器鏈(InterceptorChain),用于支持插件擴展。

2. SqlSessionFactory(會話工廠)

功能概述
SqlSessionFactory?是 MyBatis 的核心工廠類,用于創建?SqlSession?實例。它是線程安全的,通常在應用啟動時初始化。

核心職責

  • 根據?Configuration?創建?SqlSession

  • 管理數據庫連接池和事務工廠。

  • 加載 Mapper 文件和對應的映射語句。

3. SqlSession(會話)

功能概述
SqlSession?是 MyBatis 與數據庫交互的核心接口,提供了執行 SQL、管理事務、獲取 Mapper 接口等功能。

核心職責

  • 執行 CRUD 操作(selectinsertupdatedelete)。

  • 提供事務管理、批量操作、緩存等功能。

  • 獲取 Mapper 接口的代理對象。

4. Executor(執行器)

功能概述
Executor 是 MyBatis 的內部執行器,負責執行 SQL 語句并處理結果。它是 MyBatis 中的核心對象之一,負責查詢、插入、更新等數據庫操作。

核心職責

  • 調用 StatementHandler 執行 SQL 語句。

  • 管理一級緩存和二級緩存。

  • 觸發攔截器鏈,支持插件擴展。

5. StatementHandler(語句處理器)

功能概述

StatementHandler 負責將 SQL 語句發送到數據庫執行,主要完成 SQL 的解析、生成和執行。

核心職責

  • 創建?PreparedStatement?并設置參數。

  • 執行 SQL 并返回結果。

  • 支持一級緩存,避免重復查詢。

6. ParameterHandler(參數處理器)

功能概述
ParameterHandler?負責將 Java 對象轉換為 JDBC 參數,并設置到?PreparedStatement?中。

核心職責

  • 處理 SQL 參數的映射。

  • 將 Java 對象轉換為 JDBC 所需的參數格式。

7. ResultSetHandler(結果集處理器)

功能概述
ResultSetHandler?負責將 JDBC 返回的?ResultSet?轉換為 Java 對象集合。

核心職責

  • 解析?ResultSet?結果集。

  • 將數據庫查詢結果映射為 Java 對象。

8. TypeHandler(類型處理器)

功能概述
TypeHandler?用于 Java 類型和數據庫類型之間的轉換。負責將 Java 對象的屬性值轉換為數據庫支持的數據類型,或者將數據庫查詢結果轉化為 Java 對象。

核心職責

  • 處理 Java 類型與 JDBC 類型的映射。

  • 支持自定義類型轉換。

9. MappedStatement(映射語句)

功能概述
MappedStatement負責封裝單個 <select>、<insert>、<update>、<delete> 等 SQL 語句的配置信息。包括 SQL 類型、參數映射、結果映射等。

核心職責

  • 存儲 SQL 語句、參數映射、返回值映射等信息。

  • 提供 SQL 執行的上下文信息。

10. SqlSource(SQL 源)

功能概述
SqlSource?負責根據傳入的 parameterObject 動態生成 SQL 語句。它通過 BoundSql 對象封裝 SQL 和參數,并返回給 StatementHandler 執行。

核心職責

  • 動態生成 SQL 語句。

  • 根據傳入的參數對象構建 SQL 語句,并封裝到 BoundSql 中。

11. BoundSql(封裝的 SQL)

功能概述

BoundSql 負責封裝動態生成的 SQL 語句和參數信息。它是 SqlSource 生成 SQL 后的載體,存儲了 SQL 語句及其相關參數信息。

核心職責

  • 存儲動態生成的 SQL 語句。

  • 提供 SQL 執行所需的參數信息。

"優秀的框架設計應該像電路板一樣,允許開發者在不修改核心邏輯的情況下,通過插件機制擴展功能。" —— MyBatis設計哲學

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/895820.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/895820.shtml
英文地址,請注明出處:http://en.pswp.cn/news/895820.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

137,【4】 buuctf web [SCTF2019]Flag Shop

進入靶場 都點擊看看 發現點擊work會增加&#xffe5; 但肯定不能一直點下去 抓包看看 這看起來是一個 JWT&#xff08;JSON Web Token&#xff09;字符串。JWT 通常由三部分組成&#xff0c;通過點&#xff08;.&#xff09;分隔&#xff0c;分別是頭部&#xff08;Header&…

twisted實現MMORPG 游戲數據庫操作封裝設計與實現

在設計 MMORPG&#xff08;大規模多人在線角色扮演游戲&#xff09;時&#xff0c;數據庫系統是游戲架構中至關重要的一部分。數據庫不僅承擔了游戲中各種數據&#xff08;如玩家數據、物品數據、游戲世界狀態等&#xff09;的存儲和管理任務&#xff0c;還必須高效地支持并發訪…

【R語言】聚類分析

聚類分析是一種常用的無監督學習方法&#xff0c;是將所觀測的事物或者指標進行分類的一種統計分析方法&#xff0c;其目的是通過辨認在某些特征上相似的事物&#xff0c;并將它們分成各種類別。R語言提供了多種聚類分析的方法和包。 方法優點缺點適用場景K-means計算效率高需…

超全Deepseek資料包,deepseek下載安裝部署提示詞及本地部署指南介紹

該資料包涵蓋了DeepSeek模型的下載、安裝、部署以及本地運行的詳細指南&#xff0c;適合希望在本地環境中高效運行DeepSeek模型的用戶。資料包不僅包括基礎的安裝步驟&#xff0c;還提供了68G多套獨立部署視頻教程教程&#xff0c;針對不同硬件配置的模型選擇建議&#xff0c;以…

Java Spring boot 篇:常用注解

Configuration 作用 Configuration 注解的核心作用是把一個類標記為 Spring 應用上下文里的配置類。配置類就像一個 Java 版的 XML 配置文件&#xff0c;能夠在其中定義 Bean 定義和 Bean 之間的依賴關系。當 Spring 容器啟動時&#xff0c;會掃描這些配置類&#xff0c;解析其…

在 Ubuntu 20.04 為 Clash Verge AppImage 創建桌面圖標教程

在 Ubuntu 20.04 為 AppImage 創建桌面圖標教程 一、準備工作 確保你已經下載了 xxxx.AppImage 文件&#xff0c;并且知道它所在的具體路徑。同時&#xff0c;你可以準備一個合適的圖標文件&#xff08;.png 格式&#xff09;用于代表該應用程序&#xff0c;如果沒有合適的圖…

【復現DeepSeek-R1之Open R1實戰】系列6:GRPO源碼逐行深度解析(上)

目錄 4 GRPO源碼分析4.1 數據類 GRPOScriptArguments4.2 系統提示字符串 SYSTEM_PROMPT4.3 獎勵函數4.3.1 accuracy_reward函數4.3.2 verify函數4.3.3 format_reward函數 4.4 將數據集格式化為對話形式4.5 初始化GRPO Trainer 【復現DeepSeek-R1之Open R1實戰】系列3&#xff1…

【雜談】加油!!!!

為了在三月底前系統準備Java后端開發的面試和筆試&#xff0c;以下是分階段的高效學習計劃&#xff1a; 一、知識體系構建&#xff08;第1-2周&#xff09; 核心基礎強化 Java基礎&#xff08;每日1.5小時&#xff09;&#xff1a; 重點掌握&#xff1a;JVM內存模型&#xff0…

python旅游推薦系統+爬蟲+可視化(協同過濾算法)

??基于用戶的協同過濾算法 ??有后臺管理 ??2w多數據集 這個旅游數據分析推薦系統采用了Python語言、Django框架、MySQL數據庫、requests庫進行網絡爬蟲開發、機器學習中的協同過濾算法、ECharts數據可視化技術&#xff0c;以實現從網站抓取旅游數據、個性化推薦和直觀展…

HarmonyNext上傳用戶相冊圖片到服務器

圖片選擇就不用說了&#xff0c;直接用 無須申請權限 。 上傳圖片&#xff0c;步驟和android對比稍微有點復雜&#xff0c;可能是為了安全性考慮&#xff0c;需要將圖片先拷貝到緩存目錄下面&#xff0c;然后再上傳&#xff0c;當然你也可以轉成Base64&#xff0c;然后和服務…

同為科技智能PDU助力Deepseek人工智能和數據交互的快速發展

1 2025開年&#xff0c;人工智能領域迎來了一場前所未有的變革。Deepseek成為代表“東方力量”的開年王炸&#xff0c;不僅在國內掀起了技術熱潮&#xff0c;并且在全球范圍內引起了高度關注。Deepseek以顛覆性技術突破和現象級應用場景席卷全球&#xff0c;這不僅重塑了產業格…

二、QEMU NFS 環境搭建

? 在上一章節中&#xff0c;我們已經成功完成了內核和 busybox 環境的配置。為了進一步提高開發效率&#xff0c;我們可以使用 NFS&#xff08;Network File System&#xff09;來掛載根目錄。NFS 允許我們將本地文件系統通過網絡共享給虛擬機使用&#xff0c;這樣在開發過程中…

.NET 9.0 的 Blazor Web App 項目中 EF Core 【事務】使用備忘

一、DbContext.Database.BeginTransactionAsync() 模式 1. 注意事項&#xff1a;連接字符串中啟用了 MARS&#xff08;Multiple Active Result Sets&#xff1a;MultipleActiveResultSetsTrue &#xff09;后&#xff0c;無法創建 保存點&#xff08;保存點與 SQL Server 的多…

記一次 Git Fetch 后切換分支為空的情況

Git Fetch 后切換分支為空的情況 在使用 Git 時&#xff0c;我遇到這樣的情況&#xff1a;執行 git fetch 后切換分支&#xff0c;發現工作目錄是空的&#xff0c;沒有任何文件&#xff0c;所以插眼記錄一下。 原因分析 git fetch 的作用&#xff1a;git fetch 只會從遠程倉庫…

UMLS數據下載及訪問

UMLS數據申請 這個直接在官網上申請即可&#xff0c;記得把地址填全&#xff0c;基本都會拿到lisence。 UMLS數據訪問 UMLS的數據訪問分為網頁訪問&#xff0c;API訪問以及數據下載后的本地訪問&#xff0c;網頁訪問&#xff0c;API訪問按照官網的指示即可&#xff0c;這里主…

使用 Docker 部署 Apache Spark 集群教程

簡介 Apache Spark 是一個強大的統一分析引擎&#xff0c;用于大規模數據處理。本文將詳細介紹如何使用 Docker 和 Docker Compose 快速部署一個包含一個 Master 節點和兩個 Worker 節點的 Spark 集群。這種方法不僅簡化了集群的搭建過程&#xff0c;還提供了資源隔離、易于擴…

瑞薩RA-T系列芯片ADCGPT功能模塊的配合使用

在馬達或電源工程中&#xff0c;往往需要采集多路AD信號&#xff0c;且這些信號的優先級和采樣時機不相同。本篇介紹在使用RA-T系列芯片建立馬達或電源工程時&#xff0c;如何根據需求來設置主要功能模塊ADC&GPT&#xff0c;包括采樣通道打包和分組&#xff0c;GPT觸發啟動…

20250217 隨筆 redis非原子性操作簡述

從你提供的文本來看&#xff0c;核心是 Redis 作為緩存的檢查機制&#xff0c;以及非原子性操作導致的不一致性問題。 我們可以拆解為兩個部分來理解&#xff1a; &#x1f4cc; 1. 邏輯&#xff1a;先查 Redis&#xff0c;再決定是否注冊 邏輯流程 先查詢 Redis 是否有某個 …

git-提交時間和作者時間的區別

1.介紹 定義介紹 提交時間&#xff08;Committer Date&#xff09;&#xff1a;決定了提交在 Git 歷史中的位置&#xff0c;通常影響 GitHub 上提交顯示的順序。 作者時間&#xff08;Author Date&#xff09;&#xff1a;雖然不影響提交的排序&#xff0c;但在每個提交詳情頁…

PHP框架入門指南:從零構建現代Web應用

一、為什么需要PHP框架? 1.1 傳統PHP開發的痛點 重復造輪子:用戶認證、表單驗證等基礎功能需要反復開發代碼混亂:缺乏統一結構導致維護困難安全漏洞:手動處理SQL注入/XSS攻擊效率低下擴展性差:耦合代碼難以適應業務增長1.2 框架的核心價值 標準化架構:MVC模式強制代碼分…