亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

博主記得在一個(gè)周五快下班的下午,產(chǎn)品找到我,跟我說(shuō)有幾個(gè)業(yè)務(wù)列表查詢需要加上時(shí)間條件過(guò)濾數(shù)據(jù),這個(gè)條件可能會(huì)變,不保證以后不修改,這個(gè)改動(dòng)涉及到多個(gè)列表查詢,于是博主思考了一會(huì)想了幾種實(shí)現(xiàn)方案,

  1. 最簡(jiǎn)單,直接將時(shí)間條件寫死,由 Service 層傳遞給 Dao 層進(jìn)行條件拼接。實(shí)現(xiàn)上雖然簡(jiǎn)單,但是代碼上感覺(jué)非常 low,如果這個(gè)參數(shù)需要在很多方法里進(jìn)行傳遞,那么工作量就比較大。
  2. 復(fù)雜一點(diǎn),通過(guò) MyBatis 的攔截器機(jī)制,在 SQL 拼接的 prepare 階段修改 SQL 語(yǔ)句,實(shí)現(xiàn)動(dòng)態(tài) SQL。

考慮到攔截器機(jī)制不需要修改過(guò)多代碼,因此本文博主將帶領(lǐng)大家學(xué)習(xí)如何利用 MyBatis 攔截器機(jī)制來(lái)優(yōu)雅的實(shí)現(xiàn)這個(gè)需求。

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

簡(jiǎn)介

MyBatis 是一個(gè)流行的 JAVA 持久層框架,它提供了靈活的 SQL 映射和執(zhí)行功能。有時(shí)候我們可能需要在運(yùn)行時(shí)動(dòng)態(tài)地修改 SQL 語(yǔ)句,例如添加一些條件、排序、分頁(yè)等。MyBatis 提供了一個(gè)強(qiáng)大的機(jī)制來(lái)實(shí)現(xiàn)這個(gè)需求,那就是攔截器(Interceptor)。

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

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

攔截器介紹

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

  • Executor:攔截執(zhí)行器的方法,例如 update、query、commit、rollback 等。可以用來(lái)實(shí)現(xiàn)緩存、事務(wù)、分頁(yè)等功能。
  • ParameterHandler:攔截參數(shù)處理器的方法,例如 setParameters 等。可以用來(lái)轉(zhuǎn)換或加密參數(shù)等功能。
  • ResultSetHandler:攔截結(jié)果集處理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用來(lái)轉(zhuǎn)換或過(guò)濾結(jié)果集等功能。
  • StatementHandler:攔截語(yǔ)句處理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用來(lái)修改 SQL 語(yǔ)句、添加參數(shù)、記錄日志等功能。

實(shí)現(xiàn)攔截器

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

注冊(cè)攔截器

Spring Boot 項(xiàng)目中集成了 Mybatis Plus 后要讓攔截器生效很簡(jiǎn)單,Mybatis Plus 的自動(dòng)配置類會(huì)讀取項(xiàng)目中所有注冊(cè)到 Spring 容器的攔截器并進(jìn)行自動(dòng)注冊(cè)。如下圖,MybatisPlusAutoConfiguration

圖片

圖片
注冊(cè)攔截器

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

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

代碼示例

yml 配置

指定 xml 文件中需要替換的占位符標(biāo)識(shí):@dynamicSql 以及待替換日期條件。

# 動(dòng)態(tài)sql配置
dynamicSql:
  placeholder: "@dynamicSql"
  date: "2023-07-10 20:10:30"

Dao 層代碼

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

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

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

現(xiàn)在我們對(duì)攔截器核心代碼邏輯進(jìn)行講解:

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

代碼測(cè)試

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

    @Autowired
    private DynamicSqlMapper dynamicSqlMapper;

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

執(zhí)行結(jié)果:

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'

攔截器應(yīng)用場(chǎng)景

  • SQL 語(yǔ)句執(zhí)行監(jiān)控:可以攔截執(zhí)行的 SQL 方法,打印執(zhí)行的 SQL 語(yǔ)句、參數(shù)等信息,并且還能夠記錄執(zhí)行的總耗時(shí),可供后期的 SQL 分析時(shí)使用。
  • SQL 分頁(yè)查詢:MyBatis 中使用的 RowBounds 使用的內(nèi)存分頁(yè),在分頁(yè)前會(huì)查詢所有符合條件的數(shù)據(jù),在數(shù)據(jù)量大的情況下性能較差。通過(guò)攔截器,可以在查詢前修改 SQL 語(yǔ)句,提前加上需要的分頁(yè)參數(shù)。
  • 公共字段的賦值:在數(shù)據(jù)庫(kù)中通常會(huì)有 createTime , updateTime 等公共字段,這類字段可以通過(guò)攔截統(tǒng)一對(duì)參數(shù)進(jìn)行的賦值,從而省去手工通過(guò) set 方法賦值的繁瑣過(guò)程。
  • 數(shù)據(jù)權(quán)限過(guò)濾:在很多系統(tǒng)中,不同的用戶可能擁有不同的數(shù)據(jù)訪問(wèn)權(quán)限,例如在多租戶的系統(tǒng)中,要做到租戶間的數(shù)據(jù)隔離,每個(gè)租戶只能訪問(wèn)到自己的數(shù)據(jù),通過(guò)攔截器改寫 SQL 語(yǔ)句及參數(shù),能夠?qū)崿F(xiàn)對(duì)數(shù)據(jù)的自動(dòng)過(guò)濾。
  • SQL 語(yǔ)句替換:對(duì) SQL 中條件或者特殊字符進(jìn)行邏輯替換。(也是本文的應(yīng)用場(chǎng)景)

總結(jié)

到此本文講解的 MyBatis 實(shí)現(xiàn)動(dòng)態(tài) SQL 內(nèi)容就講解完畢了,希望大家喜歡。

分享到:
標(biāo)簽:MyBatis
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定