日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

博主記得在一個周五快下班的下午,產品找到我,跟我說有幾個業務列表查詢需要加上時間條件過濾數據,這個條件可能會變,不保證以后不修改,這個改動涉及到多個列表查詢,于是博主思考了一會想了幾種實現方案,

  1. 最簡單,直接將時間條件寫死,由 Service 層傳遞給 Dao 層進行條件拼接。實現上雖然簡單,但是代碼上感覺非常 low,如果這個參數需要在很多方法里進行傳遞,那么工作量就比較大。
  2. 復雜一點,通過 MyBatis 的攔截器機制,在 SQL 拼接的 prepare 階段修改 SQL 語句,實現動態 SQL。

考慮到攔截器機制不需要修改過多代碼,因此本文博主將帶領大家學習如何利用 MyBatis 攔截器機制來優雅的實現這個需求。

本文示例代碼全部在 Spring Boot3.0、Mybatis Plus3.5.3.1 版本下運行。

簡介

MyBatis 是一個流行的 JAVA 持久層框架,它提供了靈活的 SQL 映射和執行功能。有時候我們可能需要在運行時動態地修改 SQL 語句,例如添加一些條件、排序、分頁等。MyBatis 提供了一個強大的機制來實現這個需求,那就是攔截器(Interceptor)。

推薦博主開源的 H5 商城項目waynboot-mall,這是一套全部開源的微商城項目,包含三個項目:運營后臺、H5 商城前臺和服務端接口。實現了商城所需的首頁展示、商品分類、商品詳情、商品 sku、分詞搜索、購物車、結算下單、支付寶/微信支付、收單評論以及完善的后臺管理等一系列功能。技術上基于最新得 Springboot3.0、jdk17,整合了 MySQL、redis、RabbitMQ、ElasticSearch 等常用中間件。分模塊設計、簡潔易維護,歡迎大家點個 star、關注博主。

Github 地址:https://github.com/wayn111/waynboot-mall

攔截器介紹

攔截器是一種基于 AOP(面向切面編程)的技術,它可以在目標對象的方法執行前后插入自定義的邏輯。MyBatis 定義了四種類型的攔截器,分別是:

  • Executor:攔截執行器的方法,例如 update、query、commit、rollback 等。可以用來實現緩存、事務、分頁等功能。
  • ParameterHandler:攔截參數處理器的方法,例如 setParameters 等。可以用來轉換或加密參數等功能。
  • ResultSetHandler:攔截結果集處理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用來轉換或過濾結果集等功能。
  • StatementHandler:攔截語句處理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用來修改 SQL 語句、添加參數、記錄日志等功能。

實現攔截器

  1. 定義一個實現 org.Apache.ibatis.plugin.Interceptor 接口的攔截器類,并重寫其中的 intercept、plugin 和 setProperties 方法。
  2. 添加 @Intercepts 注解,寫上需要攔截的對象和方法,以及方法參數,例如 @Intercepts({@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})}),表示在 SQL 執行之前進行攔截處理。

注冊攔截器

Spring Boot 項目中集成了 Mybatis Plus 后要讓攔截器生效很簡單,Mybatis Plus 的自動配置類會讀取項目中所有注冊到 Spring 容器的攔截器并進行自動注冊。如下圖,MybatisPlusAutoConfiguration

圖片

圖片
注冊攔截器

所以我們只需要定義一個 DynamicSqlInterceptor 攔截器并加上 @Component 注解就行,代碼如下,

@Component
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class DynamicSqlInterceptor implements Interceptor {
 ...
}

代碼示例

yml 配置

指定 xml 文件中需要替換的占位符標識:@dynamicSql 以及待替換日期條件。

# 動態sql配置
dynamicSql:
  placeholder: "@dynamicSql"
  date: "2023-07-10 20:10:30"

Dao 層代碼

在需要進行 SQL 占位符替換的方法上加 @DynamicSql 注解。

public interface DynamicSqlMApper {
    @DynamicSql
    Long count();
}

mapper 文件

將日期條件改成占位符 where create_time > @dynamicSql

<mapper namespace="ltd.newbee.mall.core.dao.DynamicSqlMapper">
    <select id="count" resultType="java.lang.Long">
        select count(1) from member
        where create_time > @dynamicSql
    </select>
</mapper>

攔截器核心代碼

@Component
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, 
                method = "prepare", args = {Connection.class, Integer.class})
})
public class DynamicSqlInterceptor implements Interceptor {

    @Value("${dynamicSql.placeholder}")
    private String placeholder;

    @Value("${dynamicSql.date}")
    private  String dynamicDate;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 獲取 StatementHandler 對象也就是執行語句
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 2. MetaObject 是 MyBatis 提供的一個反射幫助類,可以優雅訪問對象的屬性,這里是對 statementHandler 對象進行反射處理,
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                        new DefaultReflectorFactory());
        // 3. 通過 metaObject 反射獲取 statementHandler 對象的成員變量 mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // mappedStatement 對象的 id 方法返回執行的 mapper 方法的全路徑名,如ltd.newbee.mall.core.dao.UserMapper.insertUser
        String id = mappedStatement.getId();
        // 4. 通過 id 獲取到 Dao 層類的全限定名稱,然后反射獲取 Class 對象
        Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
        // 5. 獲取包含原始 sql 語句的 BoundSql 對象
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        log.info("替換前---sql:{}", sql);
        // 攔截方法
        String mSql = null;
        // 6. 遍歷 Dao 層類的方法
        for (Method method : classType.getMethods()) {
            // 7. 判斷方法上是否有 DynamicSql 注解,有的話,就認為需要進行 sql 替換
            if (method.isAnnotationPresent(DynamicSql.class)) {
                mSql = sql.replaceAll(placeholder, String.format("'%s'", dynamicDate));
                break;
            }
        }
        if (StringUtils.isNotBlank(mSql)) {
            log.info("替換后---mSql:{}", mSql);
            // 8. 對 BoundSql 對象通過反射修改 SQL 語句。
            Field field = boundSql.getClass().getDeclaredField("sql");
            field.setAccessible(true);
            field.set(boundSql, mSql);
        }
        // 9. 執行修改后的 SQL 語句。
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 使用 Plugin.wrap 方法生成代理對象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 獲取配置文件中的屬性值
    }
}

現在我們對攔截器核心代碼邏輯進行講解:

  1. 通過 invocation 參數獲取 statementHandler 對象,也就是包含拼接后 SQL 語句的對象。
  2. 獲取 metaObject 對象, MetaObject 是 MyBatis 提供的一個反射幫助類,可以優雅訪問對象的屬性,這里是訪問 statementHandler 對象進行反射處理。
  3. 通過 metaObject 反射獲取 statementHandler 對象的成員變量 mappedStatement。
  4. 通過 mappedStatement 對象的 id 方法獲取到 Dao 層類的全限定名稱,然后反射獲取 Dao 層類的 Class 對象。
  5. 獲取包含原始 SQL 語句的 BoundSql 對象。
  6. 遍歷 Dao 層類的方法。
  7. 判斷方法上是否有 DynamicSql 注解,有的話就進行時間條件替換。
  8. 對 BoundSql 對象通過反射修改 SQL 語句。
  9. 執行修改后的 SQL 語句。

代碼測試

// 測試類
@SpringBootTest
@RunWith(SpringRunner.class)
public class DynamicTest {

    @Autowired
    private DynamicSqlMapper dynamicSqlMapper;

    @Test
    public void test() {
        Long count = dynamicSqlMapper.count();
        Assert.notNull(count, "count不能為null");
    }
}

執行結果:

2023-07-11 22:13:33.375 [mAIn] INFO  l.n.m.config.DynamicSqlInterceptor - [intercept,52] - 替換前---sql:select count(1) from member
        where create_time > @dynamicSql
2023-07-11 22:13:33.376 [main] INFO  l.n.m.config.DynamicSqlInterceptor - [intercept,62] - 替換后---mSql:select count(1) from member
        where create_time > '2023-07-10 20:10:30'

攔截器應用場景

  • SQL 語句執行監控:可以攔截執行的 SQL 方法,打印執行的 SQL 語句、參數等信息,并且還能夠記錄執行的總耗時,可供后期的 SQL 分析時使用。
  • SQL 分頁查詢:MyBatis 中使用的 RowBounds 使用的內存分頁,在分頁前會查詢所有符合條件的數據,在數據量大的情況下性能較差。通過攔截器,可以在查詢前修改 SQL 語句,提前加上需要的分頁參數。
  • 公共字段的賦值:在數據庫中通常會有 createTime , updateTime 等公共字段,這類字段可以通過攔截統一對參數進行的賦值,從而省去手工通過 set 方法賦值的繁瑣過程。
  • 數據權限過濾:在很多系統中,不同的用戶可能擁有不同的數據訪問權限,例如在多租戶的系統中,要做到租戶間的數據隔離,每個租戶只能訪問到自己的數據,通過攔截器改寫 SQL 語句及參數,能夠實現對數據的自動過濾。
  • SQL 語句替換:對 SQL 中條件或者特殊字符進行邏輯替換。(也是本文的應用場景)

總結

到此本文講解的 MyBatis 實現動態 SQL 內容就講解完畢了,希望大家喜歡。

分享到:
標簽:MyBatis
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定