欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mybatis之通用Mapper動(dòng)態(tài)表名及其原理分析

 更新時(shí)間:2023年08月29日 10:51:18   作者:tingmailang  
這篇文章主要介紹了Mybatis之通用Mapper動(dòng)態(tài)表名及其原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

一、引言

單表增刪改查的重復(fù)書(shū)寫(xiě)相當(dāng)冗余,目前為了避免這樣的冗余我們會(huì)使用通用mapper,但是當(dāng)遇到表名動(dòng)態(tài)變化的時(shí)候,比如按年、月、天分表就需要寫(xiě)常規(guī)的增刪改查sql,這時(shí)候就會(huì)失去通用mapper單表不用寫(xiě)sql的優(yōu)勢(shì)。

此時(shí)可以使用通用Mapper動(dòng)態(tài)攔截器操作表名。

二、使用

1、枚舉類(lèi)

@Getter
public enum TableEnum {
    UNSERVICEDAY("t_mac_unservice_day", "未運(yùn)營(yíng)日?qǐng)?bào)"),
    SERVICEDAY("t_mac_service_day", "運(yùn)營(yíng)日?qǐng)?bào)"),
    ;
    private String table;
    private String desc;
    TableEnum(String table, String desc) {
        this.table = table;
        this.desc = desc;
    }
    public static TableEnum of(String value) {
        Optional<TableEnum> assetEventEnum = Arrays.stream(TableEnum.values())
                .filter(c -> Objects.equals(c.getTable(),value)).findFirst();
        return assetEventEnum.orElse(null);
    }
}

2、攔截器 

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
        dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {
            //表名操作
            TableEnum tableEnum = TableEnum.of(tableName);
            switch (tableEnum) {
                case SERVICEDAY:
                case UNSERVICEDAY:
                    return tableName + CommonConstant.SPLIT_CHAR_ + LocalDateTime.now().minusDays(CommonConstant.ONE).format(DateTimeUtil.YYYYMMDD_FORMATTER);
                default:
                    return tableName;
            }
        });
        //加入Mybatis的攔截器
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        return interceptor;
    }
}

3、部分sql不攔截

雖然大部分的sql都是對(duì)當(dāng)天的表進(jìn)行操作,但是總有操作不是針對(duì)當(dāng)天的,例如創(chuàng)建、刪除表、查詢(xún)過(guò)往數(shù)據(jù)。

初期走了一些彎路,本來(lái)是想使用@MapperScan({"***.domain.mapper"})限制這個(gè)攔截配置類(lèi)的作用范圍,將攔截限制在固定路徑下,然后將不需要攔截的單獨(dú)在其他路徑下編寫(xiě)。

但是這個(gè)攔截器是注冊(cè)在Mybatis內(nèi)部,底層還是使用Mybatis的攔截sql機(jī)制,所以限制作用范圍是不起作用的,具體內(nèi)容感興趣的可以看原理分析。

回歸正題,那么如果不攔截該sql呢?通過(guò)查閱通用Mapper的相關(guān)文檔了解到有一個(gè)注解可以使用。

對(duì)于通用Mapper提供的動(dòng)態(tài)表名、行級(jí)租戶(hù)等多種功能都可以進(jìn)行忽略政策,加在Mapper層的方法上就可以避免攔截。

public @interface InterceptorIgnore {
    /**
     * 行級(jí)租戶(hù) {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor}
     */
    String tenantLine() default "";
    /**
     * 動(dòng)態(tài)表名 {@link com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor}
     */
    String dynamicTableName() default "";
    /**
     * 攻擊 SQL 阻斷解析器,防止全表更新與刪除 {@link com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor}
     */
    String blockAttack() default "";
    /**
     * 垃圾SQL攔截 {@link com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor}
     */
    String illegalSql() default "";
    /**
     * 數(shù)據(jù)權(quán)限 {@link com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor}
     * <p>
     * 默認(rèn)關(guān)閉,需要注解打開(kāi)
     */
    String dataPermission() default "1";
    /**
     * 分表 {@link com.baomidou.mybatisplus.extension.plugins.inner.ShardingInnerInterceptor}
     */
    String sharding() default "";
    /**
     * 其他的
     * <p>
     * 格式應(yīng)該為:  "key"+"@"+可選項(xiàng)[false,true,1,0,on,off]
     * 例如: "xxx@1" 或 "xxx@true" 或 "xxx@on"
     * <p>
     * 如果配置了該屬性的注解是注解在 Mapper 上的,則如果該 Mapper 的一部分 Method 需要取反則需要在 Method 上注解并配置此屬性為反值
     * 例如: "xxx@1" 在 Mapper 上, 則 Method 上需要 "xxx@0"
     */
    String[] others() default {};
}

4、Mapper

public interface MacUnserviceDayMapper extends BaseMapper<MacUnserviceDayEntity> {
    /**
     * 分頁(yè)展示
     * @param pageQuery
     * @return
     */
    List<MacUnserviceDayEntity> pageList(@Param("query") PageQueryRequest<MacDayRequestDTO> pageQuery);
    List<MacUnserviceDayEntity> exportList(@Param("query") PageQueryRequest<MacDayRequestDTO> pageQuery);
    /**
     * 獲取分頁(yè)數(shù)量.
     *
     * @param pageQuery
     * @return
     */
    int pageCount(@Param("query")PageQueryRequest<MacDayRequestDTO> pageQuery);
    //刪除指定表
    @InterceptorIgnore(dynamicTableName = "true")
    int deleteBySelect(@Param("timeSuffix")String timeSuffix);
}

三、原理分析

總體架構(gòu)如下圖

1、Mybatis攔截模式

從下圖可以看到Mybatis對(duì)于sql方法的攔截,動(dòng)態(tài)表名等攔截器實(shí)際上只是注冊(cè)到了它的局部變量interceptors中,所以在Mybatis統(tǒng)一的攔截機(jī)制下,給注冊(cè)的攔截器設(shè)置作用范圍也就不會(huì)生效了。

@Intercepts(
    {
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        @Signature(type = StatementHandler.class, method = "getBoundSql", args = {}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)
public class MybatisPlusInterceptor implements Interceptor {
    @Setter
    private List<InnerInterceptor> interceptors = new ArrayList<>();

2、動(dòng)態(tài)表名攔截器注冊(cè)

其實(shí)只是加載到Mybatis的局部變量中

    public void addInnerInterceptor(InnerInterceptor innerInterceptor) {
        this.interceptors.add(innerInterceptor);
    }

3、攔截器生效

在intercept方法中將記載的攔截器進(jìn)行遍歷

public Object intercept(Invocation invocation) throws Throwable {
    Object target = invocation.getTarget();
    Object[] args = invocation.getArgs();
    if (target instanceof Executor) {
        final Executor executor = (Executor) target;
        Object parameter = args[1];
        boolean isUpdate = args.length == 2;
        MappedStatement ms = (MappedStatement) args[0];
        if (!isUpdate && ms.getSqlCommandType() == SqlCommandType.SELECT) {
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];
            BoundSql boundSql;
            if (args.length == 4) {
                boundSql = ms.getBoundSql(parameter);
            } else {
                boundSql = (BoundSql) args[5];
            }
            //遍歷緩存的攔截器
            for (InnerInterceptor query : interceptors) {
                if (!query.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql)) {
                    return Collections.emptyList();
                }
               //進(jìn)入查詢(xún)的前置方法
                query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            }
            CacheKey cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        } else if (isUpdate) {
            for (InnerInterceptor update : interceptors) {
                if (!update.willDoUpdate(executor, ms, parameter)) {
                    return -1;
                }
                update.beforeUpdate(executor, ms, parameter);
            }
        }
    } else {
        // StatementHandler
        final StatementHandler sh = (StatementHandler) target;
        // 目前只有StatementHandler.getBoundSql方法args才為null
        if (null == args) {
            for (InnerInterceptor innerInterceptor : interceptors) {
                innerInterceptor.beforeGetBoundSql(sh);
            }
        } else {
            Connection connections = (Connection) args[0];
            Integer transactionTimeout = (Integer) args[1];
            for (InnerInterceptor innerInterceptor : interceptors) {
                innerInterceptor.beforePrepare(sh, connections, transactionTimeout);
            }
        }
    }
    return invocation.proceed();
}

beforeQuery負(fù)責(zé)是否進(jìn)行表解析的判斷

public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
    //檢測(cè)是否忽略sql
    if (InterceptorIgnoreHelper.willIgnoreDynamicTableName(ms.getId())) return;
    //進(jìn)入sql解析
    mpBs.sql(this.changeTable(mpBs.sql()));
}

根據(jù)INTERCEPTOR_IGNORE_CACHE中的緩存判斷是否進(jìn)入攔截方法 

public static boolean willIgnore(String id, Function<InterceptorIgnoreCache, Boolean> function) {
    //獲取sql方法對(duì)應(yīng)的注解緩存
    InterceptorIgnoreCache cache = INTERCEPTOR_IGNORE_CACHE.get(id);
    if (cache == null) {
        cache = INTERCEPTOR_IGNORE_CACHE.get(id.substring(0, id.lastIndexOf(StringPool.DOT)));
    }
    if (cache != null) {
        //比較緩存檢查的的屬性,此處是dynamicTableName
        Boolean apply = function.apply(cache);
        return apply != null && apply;
    }
    return false;
}

sql解析,解析出表名進(jìn)入業(yè)務(wù)方法。

protected String changeTable(String sql) {
    ExceptionUtils.throwMpe(null == tableNameHandler, "Please implement TableNameHandler processing logic");
    //拆分sql
    TableNameParser parser = new TableNameParser(sql);
    List<TableNameParser.SqlToken> names = new ArrayList<>();
    // 表解析
    parser.accept(names::add);
    StringBuilder builder = new StringBuilder();
    int last = 0;
    for (TableNameParser.SqlToken name : names) {
        int start = name.getStart();
        if (start != last) {
            builder.append(sql, last, start);
            //進(jìn)入業(yè)務(wù)方法
            builder.append(tableNameHandler.dynamicTableName(sql, name.getValue()));
        }
        last = name.getEnd();
    }
    if (last != sql.length()) {
        builder.append(sql.substring(last));
    }
    return builder.toString();
}

表解析,獲取表名給使用者在業(yè)務(wù)方法中進(jìn)行處理。

public void accept(TableNameVisitor visitor) {
    int index = 0;
    String first = tokens.get(index).getValue();
    if (isOracleSpecialDelete(first, tokens, index)) {
    //首字符串是刪除,只支持緊跟表名
        visitNameToken(tokens.get(index + 1), visitor);
    } else if (isCreateIndex(first, tokens, index)) {
       //首字符串是創(chuàng)建,只支持創(chuàng)建索引
        visitNameToken(tokens.get(index + 4), visitor);
    } else {
        //遍歷所有字符串
        while (hasMoreTokens(tokens, index)) {
            String current = tokens.get(index++).getValue();
            if (isFromToken(current)) {
                //找到from字符串
                processFromToken(tokens, index, visitor);
            } else if (isOnDuplicateKeyUpdate(current, index)) {
                //找到duplicate字符串,后面是不是update字符串
                index = skipDuplicateKeyUpdateIndex(index);
            } else if (concerned.contains(current.toLowerCase())) {
                // 找到table、into、join、using、update字符串,認(rèn)為后續(xù)緊跟表名
                if (hasMoreTokens(tokens, index)) {
                    SqlToken next = tokens.get(index++);
                    visitNameToken(next, visitor);
                }
            }
        }
    }
}

四、總結(jié)

對(duì)于需要按照業(yè)務(wù)情況分表的情況有很對(duì),對(duì)應(yīng)的工具也有很多,本文主要是表過(guò)期就會(huì)進(jìn)行刪除,所以沒(méi)有必要使用sharding的分區(qū)方案,采用了Mybatis的動(dòng)態(tài)攔截。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java Calendar類(lèi)的時(shí)間操作

    Java Calendar類(lèi)的時(shí)間操作

    這篇文章主要為大家詳細(xì)介紹了Java Calendar類(lèi)的時(shí)間操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Java中默認(rèn)的訪問(wèn)權(quán)限作用域解析

    Java中默認(rèn)的訪問(wèn)權(quán)限作用域解析

    這篇文章主要介紹了Java中默認(rèn)的訪問(wèn)權(quán)限作用域,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 淺談virtual、abstract方法和靜態(tài)方法、靜態(tài)變量理解

    淺談virtual、abstract方法和靜態(tài)方法、靜態(tài)變量理解

    下面小編就為大家?guī)?lái)一篇淺談virtual、abstract方法和靜態(tài)方法、靜態(tài)變量理解。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • Java面試題目集錦

    Java面試題目集錦

    本文是小編日常收集整理的java面試題目,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • Java圖形界面框架AWT布局管理器詳解

    Java圖形界面框架AWT布局管理器詳解

    這篇文章主要介紹了Java圖形界面框架AWT布局管理器,AWT是最早的圖形用戶(hù)界面框架之一,它為開(kāi)發(fā)人員提供了一些基本的組件和工具,用于構(gòu)建窗口、按鈕、文本框、標(biāo)簽等圖形界面元素,需要的朋友可以參考下
    2025-04-04
  • Java實(shí)現(xiàn)文件讀取和寫(xiě)入過(guò)程解析

    Java實(shí)現(xiàn)文件讀取和寫(xiě)入過(guò)程解析

    這篇文章主要介紹了Java實(shí)現(xiàn)文件讀取和寫(xiě)入過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值。,需要的朋友可以參考下
    2019-10-10
  • Spring Boot Web 靜態(tài)文件緩存處理的方法

    Spring Boot Web 靜態(tài)文件緩存處理的方法

    本篇文章主要介紹了Spring Boot Web 靜態(tài)文件緩存處理的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • IDEA運(yùn)行SpringBoot項(xiàng)目的詳細(xì)步驟(圖文教程)

    IDEA運(yùn)行SpringBoot項(xiàng)目的詳細(xì)步驟(圖文教程)

    本文主要介紹了IDEA運(yùn)行SpringBoot項(xiàng)目的詳細(xì)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • 一看就懂 詳解JAVA泛型通配符T,E,K,V區(qū)別

    一看就懂 詳解JAVA泛型通配符T,E,K,V區(qū)別

    泛型從字面上理解,是指一個(gè)類(lèi)、接口或方法支持多種類(lèi)型,使之廣泛化、一般化和更加通用。通配符只有在修飾一個(gè)變量時(shí)會(huì)用到,使用它可方便地引用包含了多種類(lèi)型的泛型;下面我們來(lái)深入了解一下吧
    2019-06-06
  • RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動(dòng)流程

    RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動(dòng)流程

    這篇文章主要為大家介紹了RocketMQ?NameServer架構(gòu)設(shè)計(jì)啟動(dòng)流程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02

最新評(píng)論