黄色网址大全免费-黄色网址你懂得-黄色网址你懂的-黄色网址有那些-免费超爽视频-免费大片黄国产在线观看

專注Java教育14年 全國咨詢/投訴熱線:400-8080-105
動力節(jié)點(diǎn)LOGO圖
始于2009,口口相傳的Java黃埔軍校
首頁 hot資訊 基于MyBatis分表的實(shí)現(xiàn)

基于MyBatis分表的實(shí)現(xiàn)

更新時間:2022-03-23 10:38:05 來源:動力節(jié)點(diǎn) 瀏覽3043次

1.大體思路

基于業(yè)務(wù)來看,想要按月分表,因此數(shù)據(jù)庫表里增加了一個string類型字段 account_month 來記錄月份,分表字段就使用account_month。

分表表名:表名_年月 例如明細(xì)表:ebs_date_detail_201607。

分表是一月一張表,分表的建立就是默認(rèn)建立了12個分表,如果超出了,后續(xù)再手工添加吧。也可以寫個腳本每月底創(chuàng)建下一個月的表,但是覺得沒啥必要。就算哪天忘記添加了,代碼邏輯的異常處理流程里面也能夠保證我的數(shù)據(jù)不丟失,啟動一下異常數(shù)據(jù)處理也就妥妥的了。

sql語言里面會要求帶上分表字段,通過分表字段計(jì)算得到分表的表名,然后替換掉原來的sql,直接將數(shù)據(jù)路由到指定的分表就行了。

聽起來好像很簡單的樣子,那么就這么出發(fā)吧。

2.問題目錄

分表開始之前的問題:

Mybatis如何找到我們新增的攔截服務(wù)。

自定義的攔截服務(wù)應(yīng)該在什么時間攔截查詢動作。即什么時間截斷Mybatis執(zhí)行流。

自定義的攔截服務(wù)應(yīng)該攔截什么樣的對象。不能攔截什么樣的對象。

自定義的攔截服務(wù)攔截的對象應(yīng)該具有什么動作才能被攔截。

自定義的攔截服務(wù)如何獲取上下文中傳入的參數(shù)信息。

如何把簡單查詢,神不知鬼不覺的,無侵入性的替換為分表查詢語句。

最后,攔截器應(yīng)該如何交還被截斷的Mybatis執(zhí)行流。

帶著這些問題,我們來看看我們自定義的攔截服務(wù)是如何實(shí)現(xiàn)的。

3.逐步實(shí)現(xiàn)

(1)Mybatis如何找到我們新增的攔截服務(wù)

對于攔截器Mybatis為我們提供了一個Interceptor接口,前面有提到,通過實(shí)現(xiàn)該接口就可以定義我們自己的攔截器。自定義的攔截器需要交給Mybatis管理,這樣才能使得Mybatis的執(zhí)行與攔截器的執(zhí)行結(jié)合在一起,即,攔截器需要注冊到mybatis-config配置文件中。

通過在Mybatis配置文件中plugins元素下的plugin元素來進(jìn)行。一個plugin對應(yīng)著一個攔截器,在plugin元素下面我們可以指定若干個property子元素。Mybatis在注冊定義的攔截器時會先把對應(yīng)攔截器下面的所有property通過Interceptor的setProperties方法注入給對應(yīng)的攔截器。

配置文件:mybatis-config.xml

<configuration>
    <plugins>
        <plugin interceptor="com.selicoco.sango.common.database.paginator.interceptor.ShardTableInterceptor">
        </plugin>
    </plugins>
</configuration>

(2)什么時間截斷Mybatis執(zhí)行流

Mybatis允許我們能夠進(jìn)行切入的點(diǎn):

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

因?yàn)槲沂窍胍ㄟ^替換原來SQL中的表名來實(shí)現(xiàn)分表,包括查詢,新增,刪除等操作,所以攔截的合理時機(jī)選在StatementHandler中prepare。

執(zhí)行流在PreparedStatementHandler.instantiateStatement()方法中 return connection.prepareStatement(sql); 最終真正的執(zhí)行了語句。

所以攔截器的注解內(nèi)容:

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) }) 

(3)應(yīng)該攔截什么樣的對象

并不是所有的表都進(jìn)行了分表,也不是所有的表都需要攔截處理。所以我們要根據(jù)某些配置來確定哪些需要被處理。

這里主要使用注解的方式,設(shè)置了對應(yīng)的參數(shù)。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TableSeg {
    //表名
    public String tableName();
    // 分表方式,取模,如%5:表示取5余數(shù),
    // 按時間,如MONTH:表示按月分表
    // 如果不設(shè)置,直接根據(jù)shardBy值分表
    public String shardType();
    //根據(jù)什么字段分表 ,多個字段用數(shù)學(xué)表達(dá)表示,如a+b   a-b
    public String shardBy();
    // 根據(jù)什么字段分表,多個字段用數(shù)學(xué)表達(dá)表示,如a+b   a-b
    public String shardByTable();
}

注解完成后,在mapper上去配置。如果是自定義的查詢語句和返回,沒有對應(yīng)的mapper文件,那么在對應(yīng)的dao 上進(jìn)行配置就可以了。

@TableSeg(tableName="ebs_date_detail",shardType="MONTH",shardBy="accountMonth",shardByTable="account_month")
public interface EbsDataDetailMapper {}
@Repository
@TableSeg(tableName="ebs_date_detail",shardType="MONTH",shardBy="accountMonth",shardByTable="account_month")
public class EbsDataDetailDao {}

(4)如何獲取上下文中傳入的參數(shù)

首先,如何拿到執(zhí)行前已經(jīng)組裝好的語句。分兩種情況來說,查詢和更新。

不說話先看圖:

新增數(shù)據(jù)的時候,我們從boundSql里面的additionalParameters 里面能輕松拿到注解上面 shardBy="accountMonth"所對應(yīng)的參數(shù)值。然后根據(jù)參數(shù)來生成分表語句,一切順利。

如此簡單,覺得自己好機(jī)智。開心的去碼后面的代碼了,等到單測的時候執(zhí)行查詢,然后就報錯啦。只能Debug看看。

沒有想到,都是mybatis的動態(tài)sql,結(jié)果參數(shù)方式竟然不同,想來也只能自己去取參數(shù)了。參數(shù)在哪里?看圖

具體的就看后面實(shí)現(xiàn)代碼吧,反正就是通過兩種方式取到我們要的分表字段的參數(shù)值,這樣才能求得分表表名。

(5)真正實(shí)現(xiàn)分表查詢語句

攔截器主要的作用是讀取配置,根據(jù)配置的切分策略和字段,來切分表,然后替換原執(zhí)行的SQL,從而實(shí)現(xiàn)自動切分。

        String accountMonth = genShardByValue(metaStatementHandler, mappedStatement ,tableSeg, boundSql);
        String newSql = boundSql.getSql().replace(tableSeg.tableName(), tableSeg.tableName() + "_" + accountMonth);
        if (newSql != null) {
            logger.debug(tag, "分表后SQL =====>" + newSql);
            metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
        }

(6)交還被截斷的Mybatis執(zhí)行流

把原有的簡單查詢語句替換為分表查詢語句了,現(xiàn)在是時候?qū)⒊绦虻目刂茩?quán)交還給Mybatis了

        // 傳遞給下一個攔截器處理
        return invocation.proceed();

4.實(shí)現(xiàn)源碼

(1)配置文件

見本文: 3.1 Mybatis如何找到我們新增的攔截服務(wù) -- mybatis-config.xml

(2)分表配置注解

分表注解定義、mapper注解配置、DAO注解配置

見本文: 3.3 應(yīng)該攔截什么樣的對象

(3)分表實(shí)現(xiàn)

分表具體實(shí)現(xiàn)

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class ShardTableInterceptor implements Interceptor {
    private final static Logger logger = LoggerFactory.getLogger(ShardTableInterceptor.class);
    private static final String tag = ShardTableInterceptor.class.getName();
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        String sqlId = mappedStatement.getId();
        String className = sqlId.substring(0, sqlId.lastIndexOf("."));
        Class<?> classObj = Class.forName(className);
        TableSeg tableSeg = classObj.getAnnotation(TableSeg.class);
        if(null == tableSeg){
            //不需要分表,直接傳遞給下一個攔截器處理
            return invocation.proceed();
        }?
        //根據(jù)配置獲取分表字段,生成分表SQL
        String accountMonth = genShardByValue(metaStatementHandler, mappedStatement ,tableSeg, boundSql);
        String newSql = boundSql.getSql().replace(tableSeg.tableName(), tableSeg.tableName() + "_" + accountMonth);
        if (newSql != null) {
            logger.debug(tag, "分表后SQL =====>" + newSql);
            metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
        }
        // 傳遞給下一個攔截器處理
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        // 當(dāng)目標(biāo)類是StatementHandler類型時,才包裝目標(biāo)類,否者直接返回目標(biāo)本身,減少目標(biāo)被代理的次數(shù)
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }
    @Override
    public void setProperties(Properties properties) {
        logger.info("scribeDbNames:" + properties.getProperty("scribeDbNames"));
    }
    //根據(jù)配置獲取分表的表名后綴
    private String genShardByValue(MetaObject metaStatementHandler,MappedStatement mappedStatement, TableSeg tableSeg, BoundSql boundSql) {
        String accountMonth = null;
        Map<String, Object> additionalParameters = (Map<String, Object>) metaStatementHandler.getValue("delegate.boundSql.additionalParameters");
        if (null != additionalParameters.get(tableSeg.shardBy())) {
            accountMonth = boundSql.getAdditionalParameter(tableSeg.shardBy()).toString();
        } else {
            Configuration configuration = mappedStatement.getConfiguration();
            String showSql = showSql(configuration,boundSql);
            accountMonth = getShardByValue(showSql,tableSeg);
        }
        return accountMonth;
    }
    //根據(jù)配置獲取分表參數(shù)值
    public static String getShardByValue(String showSql,TableSeg tableSeg) {
        final String conditionWhere = "where";
        String accountMonth = null ;
        if(StringUtils.isBlank(showSql)){
            return null;
        }else{
            String[] sqlSplit = showSql.toLowerCase().split(conditionWhere);
            if(sqlSplit.length>1 && sqlSplit[1].contains(tableSeg.shardByTable())){
                accountMonth = sqlSplit[1].replace(" ","").split(tableSeg.shardByTable())[1].substring(2,8);
            }
        }
        return accountMonth;
    }
    //組裝查詢語句參數(shù)
    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }else{
            return null;
        }
        return sql;
    }
    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }
        }
        return value;
    }
}

以上就是關(guān)于“基于MyBatis分表的實(shí)現(xiàn)”介紹,大家如果想了解更多相關(guān)知識,可以關(guān)注一下動力節(jié)點(diǎn)的Mybatis-Plus視頻教程,里面的課程內(nèi)容細(xì)致全面,有更豐富的知識等著大家去學(xué)習(xí),希望對大家能夠有所幫助哦。

提交申請后,顧問老師會電話與您溝通安排學(xué)習(xí)

免費(fèi)課程推薦 >>
技術(shù)文檔推薦 >>
主站蜘蛛池模板: 亚洲最大激情网 | 欧美日韩一本大道香蕉欧美 | 国产在线播放成人免费 | 日韩精品视频观看 | 亚洲成人一级 | 香蕉视频老司机 | 香蕉乱码成人久久天堂爱免费 | 成人免费视频观看 | a黄网站| 日本天堂免费观看 | 中文字幕精品在线视频 | 黄站在线 | 午夜免费福利网站 | 欧美在线视频一区 | 亚洲色欲色欲综合网站 | 120秒午夜体验区5次 | 日韩午夜视频在线观看 | 成人羞羞视频在线观看免费 | 国产精品无圣光一区二区 | 欧美精品福利在线视频 | 在线满18网站观看视频 | 玖玖精品 | 欧美成人视屏 | 天天se天天cao | 色视频线观看在线播放 | 国产99精品视频 | 一级黄色片免费 | 欧美日韩国产乱了伦 | 一级片黄色片 | 国产亚洲精品影达达兔 | 美日韩在线视频 | 国产欧美日韩视频免费61794 | 看全色黄大色大片免费 | 久久精品一区二区三区不卡牛牛 | www.深夜福利 | 日韩欧美中文字幕一区二区三区 | 国产精品欧美亚洲韩国日本99 | 久草新视频 | 免费黄色毛片 | 国产欧美日韩三级 | 国产伦一区二区三区免费 |