最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成MyBatis實(shí)現(xiàn)SQL攔截器的實(shí)戰(zhàn)指南

 更新時(shí)間:2025年07月21日 16:09:59   作者:小馬不敲代碼  
這篇文章主要為大家詳細(xì)介紹了SpringBoot集成MyBatis實(shí)現(xiàn)SQL攔截器的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下

一、為什么需要SQL攔截器?

先看幾個(gè)真實(shí)場(chǎng)景:

  • 慢查詢監(jiān)控:生產(chǎn)環(huán)境突然出現(xiàn)接口超時(shí),需要快速定位執(zhí)行時(shí)間過長(zhǎng)的SQL
  • 數(shù)據(jù)脫敏:用戶表查詢結(jié)果中的手機(jī)號(hào)、身份證號(hào)需要自動(dòng)替換為****
  • 權(quán)限控制:多租戶系統(tǒng)中,自動(dòng)給SQL添加tenant_id = ?條件,防止數(shù)據(jù)越權(quán)訪問
  • SQL審計(jì):記錄所有執(zhí)行的SQL語句、執(zhí)行人、執(zhí)行時(shí)間,滿足合規(guī)要求

如果沒有攔截器,這些需求可能需要修改每一個(gè)Mapper接口或Service方法,工作量巨大。

而MyBatis的SQL攔截器能在SQL執(zhí)行的各個(gè)階段進(jìn)行攔截處理,實(shí)現(xiàn)"無侵入式"增強(qiáng)。

二、MyBatis攔截器基礎(chǔ)

2.1 核心接口:Interceptor

MyBatis的攔截器機(jī)制基于JDK動(dòng)態(tài)代理,所有自定義攔截器都要實(shí)現(xiàn)Interceptor接口:

public interface Interceptor {
    // 攔截邏輯的核心方法
    Object intercept(Invocation invocation) throws Throwable;
    
    // 生成代理對(duì)象(通常直接用Plugin.wrap())
    Object plugin(Object target);
    
    // 讀取配置參數(shù)(如從mybatis-config.xml中獲取)
    void setProperties(Properties properties);
}

2.2 攔截目標(biāo)與簽名配置

MyBatis允許攔截4個(gè)核心組件的方法,通過@Intercepts和@Signature注解指定攔截目標(biāo):

舉個(gè)栗子:攔截StatementHandler的prepare方法(SQL預(yù)編譯階段):

@Intercepts({
    @Signature(
        type = StatementHandler.class,  // 攔截哪個(gè)接口
        method = "prepare",             // 攔截接口的哪個(gè)方法
        args = {Connection.class, Integer.class}  // 方法參數(shù)類型(用于確定重載方法)
    )
})
public class MySqlInterceptor implements Interceptor {
    // 實(shí)現(xiàn)接口方法...
}

注意:args參數(shù)必須嚴(yán)格匹配方法的參數(shù)類型,否則攔截不到!比如prepare方法有兩個(gè)重載,這里指定(Connection, Integer)類型的參數(shù)。

三、實(shí)戰(zhàn)一:慢查詢監(jiān)控?cái)r截器

3.1 需求說明

監(jiān)控所有SQL執(zhí)行時(shí)間,超過閾值(如500ms)則打印警告日志,包含:

  • SQL執(zhí)行時(shí)間
  • 完整SQL語句(帶參數(shù)占位符)
  • 參數(shù)值(防止SQL注入排查)

3.2 完整實(shí)現(xiàn)代碼

(1)攔截器類

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;

@Slf4j
@Intercepts({
    // 攔截查詢方法
    @Signature(
        type = StatementHandler.class,
        method = "query",
        args = {Statement.class, ResultHandler.class}
    ),
    // 攔截更新方法(insert/update/delete)
    @Signature(
        type = StatementHandler.class,
        method = "update",
        args = {Statement.class}
    )
})
public class SlowSqlInterceptor implements Interceptor {

    // 慢查詢閾值(毫秒),可通過配置文件注入
    private long slowThreshold = 500;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 記錄開始時(shí)間
        long startTime = System.currentTimeMillis();
        
        try {
            // 2. 執(zhí)行原方法(繼續(xù)SQL執(zhí)行流程)
            return invocation.proceed();
        } finally {
            // 3. 計(jì)算執(zhí)行耗時(shí)(無論成功失敗都記錄)
            long costTime = System.currentTimeMillis() - startTime;
            
            // 4. 獲取SQL語句和參數(shù)
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            String sql = statementHandler.getBoundSql().getSql();  // 獲取SQL語句(帶?占位符)
            Object parameterObject = statementHandler.getBoundSql().getParameterObject();  // 獲取參數(shù)
            
            // 5. 判斷是否慢查詢
            if (costTime > slowThreshold) {
                log.warn("[慢查詢警告] 執(zhí)行時(shí)間: {}ms, SQL: {}, 參數(shù): {}", 
                         costTime, sql, parameterObject);
            } else {
                log.info("[SQL監(jiān)控] 執(zhí)行時(shí)間: {}ms, SQL: {}", costTime, sql);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        // 生成代理對(duì)象(MyBatis提供的工具方法,避免自己寫代理邏輯)
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 從配置文件讀取閾值(如application.yml中配置)
        String threshold = properties.getProperty("slowThreshold");
        if (threshold != null) {
            slowThreshold = Long.parseLong(threshold);
        }
    }
}

(2)SpringBoot注冊(cè)攔截器

package com.example.config;

import com.example.interceptor.SensitiveInterceptor;
import com.example.interceptor.SlowSqlInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@MapperScan("com.example.mapper")  // Mapper接口所在包
public class MyBatisConfig {

    // 注冊(cè)慢查詢攔截器
    @Bean
    public SlowSqlInterceptor slowSqlInterceptor() {
        SlowSqlInterceptor interceptor = new SlowSqlInterceptor();
        
        // 設(shè)置屬性(也可通過application.yml配置)
        Properties properties = new Properties();
        properties.setProperty("slowThreshold", "500");  // 慢查詢閾值500ms
        interceptor.setProperties(properties);
        
        return interceptor;
    }

    @Bean
    public SensitiveInterceptor sensitiveInterceptor() {
        return new SensitiveInterceptor();
    }

    // 將攔截器添加到SqlSessionFactory
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, SlowSqlInterceptor slowSqlInterceptor) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        
        // 設(shè)置Mapper.xml路徑(如果需要)
        /*sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml")
        );*/
        
        // 添加攔截器
        sessionFactory.setPlugins(slowSqlInterceptor);

        return sessionFactory.getObject();
    }
}
(3)測(cè)試效果
寫個(gè)簡(jiǎn)單的查詢接口:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }
}

執(zhí)行后控制臺(tái)輸出:

[SQL監(jiān)控] 執(zhí)行時(shí)間: 30ms, SQL: SELECT id,username,phone FROM user WHERE id = ?
如果SQL執(zhí)行時(shí)間超過500ms(比如查詢大數(shù)據(jù)量表):

[慢查詢警告] 執(zhí)行時(shí)間: 1430ms, SQL: SELECT * FROM user WHERE id = ?, 參數(shù): {id=1, param1=1}
踩坑提示:如果攔截不到SQL,檢查@Signature注解的args參數(shù)是否與方法參數(shù)類型完全匹配!

四、實(shí)戰(zhàn)二:數(shù)據(jù)脫敏攔截器(敏感信息保護(hù))

4.1 需求說明

查詢用戶信息時(shí),自動(dòng)將敏感字段脫敏:

  • 手機(jī)號(hào):13812345678 → 138****5678
  • 身份證號(hào):110101199001011234 → ****************34

4.2 完整實(shí)現(xiàn)代碼

(1)自定義脫敏注解

import java.lang.annotation.*;

// 作用在字段上
@Target(ElementType.FIELD)
// 運(yùn)行時(shí)生效
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
    // 脫敏類型(手機(jī)號(hào)、身份證號(hào)等)
    SensitiveType type();
}

// 脫敏類型枚舉
public enum SensitiveType {
    PHONE,    // 手機(jī)號(hào)
    ID_CARD   // 身份證號(hào)
}

(2)實(shí)體類添加注解

import lombok.Data;

@Data
public class User {
    private Long id;
    private String username;
    
    @Sensitive(type = SensitiveType.PHONE)  // 手機(jī)號(hào)脫敏
    private String phone;
    
    @Sensitive(type = SensitiveType.ID_CARD)  // 身份證號(hào)脫敏
    private String idCard;
}

(3)脫敏工具類

public class SensitiveUtils {
    // 手機(jī)號(hào)脫敏:保留前3位和后4位
    public static String maskPhone(String phone) {
        if (phone == null || phone.length() != 11) {
            return phone;  // 非手機(jī)號(hào)格式不處理
        }
        return phone.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2");
    }
    
    // 身份證號(hào)脫敏:保留最后2位
    public static String maskIdCard(String idCard) {
        if (idCard == null || idCard.length() < 18) {
            return idCard;  // 非身份證格式不處理
        }
        return idCard.replaceAll("\d{16}(\d{2})", "****************$1");
    }
}

(4)結(jié)果集攔截器

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

@Slf4j
@Intercepts({
    @Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}
    )
})
public class SensitiveInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 執(zhí)行原方法,獲取查詢結(jié)果
        Object result = invocation.proceed();
        
        // 2. 如果結(jié)果是List,遍歷處理每個(gè)元素
        if (result instanceof List<?>) {
            List<?> resultList = (List<?>) result;
            for (Object obj : resultList) {
                // 3. 對(duì)有@Sensitive注解的字段進(jìn)行脫敏
                desensitize(obj);
            }
        }
        return result;
    }

    // 反射處理對(duì)象中的敏感字段
    private void desensitize(Object obj) throws IllegalAccessException {
        if (obj == null) {
            return;
        }
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();  // 獲取所有字段(包括私有)
        
        for (Field field : fields) {
            // 4. 檢查字段是否有@Sensitive注解
            if (field.isAnnotationPresent(Sensitive.class)) {
                Sensitive annotation = field.getAnnotation(Sensitive.class);
                field.setAccessible(true);  // 開啟私有字段訪問權(quán)限
                Object value = field.get(obj);  // 獲取字段值
                
                if (value instanceof String) {
                    String strValue = (String) value;
                    // 5. 根據(jù)脫敏類型處理
                    switch (annotation.type()) {
                        case PHONE:
                            field.set(obj, SensitiveUtils.maskPhone(strValue));
                            break;
                        case ID_CARD:
                            field.set(obj, SensitiveUtils.maskIdCard(strValue));
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置更多脫敏規(guī)則,此處省略
    }
}

(5)注冊(cè)多個(gè)攔截器

修改MyBatisConfig,添加脫敏攔截器:

@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
    // ... 慢查詢攔截器配置 ...

    @Bean
    public SensitiveInterceptor sensitiveInterceptor() {
        return new SensitiveInterceptor();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, 
                                             SlowSqlInterceptor slowSqlInterceptor,
                                             SensitiveInterceptor sensitiveInterceptor) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
        );
        
        // 注冊(cè)多個(gè)攔截器(注意順序!先執(zhí)行的攔截器先注冊(cè))
        sessionFactory.setPlugins(slowSqlInterceptor, sensitiveInterceptor);
        
        return sessionFactory.getObject();
    }
}

(6)測(cè)試效果

查詢用戶信息:

User user = userService.getUserById(1L);
System.out.println(user); 
// 輸出:User(id=1, username=張三, phone=138****5678, idCard=****************34)

五、實(shí)戰(zhàn)踩坑指南

5.1 攔截器順序問題

坑:多個(gè)攔截器時(shí),注冊(cè)順序就是執(zhí)行順序。比如先注冊(cè)慢查詢攔截器,再注冊(cè)脫敏攔截器:

SQL執(zhí)行 → 慢查詢攔截器(記錄時(shí)間) → 脫敏攔截器(處理結(jié)果)
如果順序反了,脫敏攔截器會(huì)先處理結(jié)果,慢查詢攔截器記錄的SQL就看不到原始參數(shù)了。

解決:按"執(zhí)行SQL前→執(zhí)行SQL后→處理結(jié)果"的順序注冊(cè)。

5.2 攔截器簽名配置錯(cuò)誤

坑:@Signature的args參數(shù)類型寫錯(cuò),導(dǎo)致攔截不到方法。比如StatementHandler.prepare方法有兩個(gè)重載:

// 正確的參數(shù)類型
prepare(Connection connection, Integer transactionTimeout)
// 錯(cuò)誤示例:寫成了(int)
@Signature(args = {Connection.class, int.class}) // 出現(xiàn)下面的異常!

java.lang.NoSuchMethodException: org.apache.ibatis.executor.statement.StatementHandler.prepare(java.sql.Connection,int)

解決:通過IDE查看方法參數(shù)類型,確保完全一致。

5.3 性能問題

坑:在攔截器中做復(fù)雜操作(如反射遍歷所有字段)會(huì)影響性能。

解決:

  • 反射操作緩存Class信息
  • 非必要不攔截(如只攔截查詢方法)
  • 敏感字段脫敏可考慮在DTO層處理

到此這篇關(guān)于SpringBoot集成MyBatis實(shí)現(xiàn)SQL攔截器的實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)SpringBoot MyBatis攔截SQL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot+輕量級(jí)分布式日志框架GrayLog實(shí)踐

    SpringBoot+輕量級(jí)分布式日志框架GrayLog實(shí)踐

    將SpringBoot日志遷移至GrayLog的實(shí)戰(zhàn)經(jīng)驗(yàn),稱其配置簡(jiǎn)單、輕量高效,支持多環(huán)境分布式,通過GELF格式實(shí)現(xiàn)快速日志追蹤與報(bào)警,尤其強(qiáng)調(diào)traceId在跨服務(wù)定位中的關(guān)鍵作用,降低維護(hù)成本并提升故障排查效率
    2025-08-08
  • 自定義一個(gè)異常類模板的簡(jiǎn)單實(shí)例

    自定義一個(gè)異常類模板的簡(jiǎn)單實(shí)例

    下面小編就為大家?guī)硪黄远x一個(gè)異常類模板的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-10-10
  • Spring避免循環(huán)依賴的策略詳解

    Spring避免循環(huán)依賴的策略詳解

    在Spring框架中,循環(huán)依賴是指兩個(gè)或多個(gè)bean相互依賴對(duì)方,形成一個(gè)閉環(huán),這在應(yīng)用啟動(dòng)時(shí)可能導(dǎo)致BeanCurrentlyInCreationException異常,本文給大家介紹了Spring中如何避免循環(huán)依賴,需要的朋友可以參考下
    2024-02-02
  • SpringMVC?Restful風(fēng)格與中文亂碼問題解決方案介紹

    SpringMVC?Restful風(fēng)格與中文亂碼問題解決方案介紹

    Restful就是一個(gè)資源定位及資源操作的風(fēng)格,不是標(biāo)準(zhǔn)也不是協(xié)議,只是一種風(fēng)格,是對(duì)http協(xié)議的詮釋,下面這篇文章主要給大家介紹了關(guān)于SpringMVC對(duì)Restful風(fēng)格支持的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • Java之如何關(guān)閉流

    Java之如何關(guān)閉流

    這篇文章主要介紹了Java之如何關(guān)閉流問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • SpringBoot Web工程同時(shí)啟動(dòng)多個(gè)HTTP端口的方法

    SpringBoot Web工程同時(shí)啟動(dòng)多個(gè)HTTP端口的方法

    本文介紹了在SpringbootWeb工程中如何配置多個(gè)HTTP端口,分別以Tomcat、Jetty和Undertow為例,通過修改配置文件和編寫配置類實(shí)現(xiàn)多端口啟動(dòng),并提供了詳細(xì)的代碼供讀者參考,需要的朋友可以參考下
    2026-04-04
  • Java初學(xué)者之五子棋游戲?qū)崿F(xiàn)教程

    Java初學(xué)者之五子棋游戲?qū)崿F(xiàn)教程

    這篇文章主要為大家詳細(xì)介紹了Java初學(xué)者之五子棋游戲?qū)崿F(xiàn)教程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • springboot導(dǎo)入多個(gè)配置文件的方法實(shí)踐

    springboot導(dǎo)入多個(gè)配置文件的方法實(shí)踐

    Spring Boot 2.4.x引入了新的配置導(dǎo)入方式,通過spring.config.import屬性可以靈活地導(dǎo)入多種類型的配置文件,本文就來介紹一下,感興趣的可以了解一下
    2024-11-11
  • Idea中如何修改項(xiàng)目的SVN地址

    Idea中如何修改項(xiàng)目的SVN地址

    這篇文章主要介紹了Idea中如何修改項(xiàng)目的SVN地址問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java NIO無法綁定指定IP和端口解決方案

    Java NIO無法綁定指定IP和端口解決方案

    這篇文章主要介紹了Java NIO無法綁定指定IP和端口解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10

最新評(píng)論

太仆寺旗| 双峰县| 梨树县| 来凤县| 宜昌市| 红原县| 灌南县| 丹阳市| 阳曲县| 麦盖提县| 亚东县| 大城县| 武安市| 黔西| 枣阳市| 阜康市| 芦山县| 滨州市| 金昌市| 孟津县| 奉贤区| 荆门市| 翁源县| 兴义市| 清苑县| 额尔古纳市| 临澧县| 万山特区| 响水县| 河北区| 齐齐哈尔市| 育儿| 定南县| 洛阳市| 玉田县| 东丰县| 靖边县| 紫金县| 嘉禾县| 广宁县| 石屏县|