大战熟女丰满人妻av-荡女精品导航-岛国aaaa级午夜福利片-岛国av动作片在线观看-岛国av无码免费无禁网站-岛国大片激情做爰视频

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

基于MyBatis分表的實現

更新時間:2022-03-23 10:38:05 來源:動力節點 瀏覽3029次

1.大體思路

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

分表表名:表名_年月 例如明細表:ebs_date_detail_201607。

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

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

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

2.問題目錄

分表開始之前的問題:

Mybatis如何找到我們新增的攔截服務。

自定義的攔截服務應該在什么時間攔截查詢動作。即什么時間截斷Mybatis執行流。

自定義的攔截服務應該攔截什么樣的對象。不能攔截什么樣的對象。

自定義的攔截服務攔截的對象應該具有什么動作才能被攔截。

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

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

最后,攔截器應該如何交還被截斷的Mybatis執行流。

帶著這些問題,我們來看看我們自定義的攔截服務是如何實現的。

3.逐步實現

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

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

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

配置文件:mybatis-config.xml

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

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

Mybatis允許我們能夠進行切入的點:

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

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

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

因為我是想要通過替換原來SQL中的表名來實現分表,包括查詢,新增,刪除等操作,所以攔截的合理時機選在StatementHandler中prepare。

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

所以攔截器的注解內容:

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

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

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

這里主要使用注解的方式,設置了對應的參數。

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

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

@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)如何獲取上下文中傳入的參數

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

不說話先看圖:

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

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

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

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

(5)真正實現分表查詢語句

攔截器主要的作用是讀取配置,根據配置的切分策略和字段,來切分表,然后替換原執行的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);
        }

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

把原有的簡單查詢語句替換為分表查詢語句了,現在是時候將程序的控制權交還給Mybatis了

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

4.實現源碼

(1)配置文件

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

(2)分表配置注解

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

見本文: 3.3 應該攔截什么樣的對象

(3)分表實現

分表具體實現

@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();
        }?
        //根據配置獲取分表字段,生成分表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) {
        // 當目標類是StatementHandler類型時,才包裝目標類,否者直接返回目標本身,減少目標被代理的次數
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }
    @Override
    public void setProperties(Properties properties) {
        logger.info("scribeDbNames:" + properties.getProperty("scribeDbNames"));
    }
    //根據配置獲取分表的表名后綴
    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;
    }
    //根據配置獲取分表參數值
    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;
    }
    //組裝查詢語句參數
    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;
    }
}

以上就是關于“基于MyBatis分表的實現”介紹,大家如果想了解更多相關知識,可以關注一下動力節點的Mybatis-Plus視頻教程,里面的課程內容細致全面,有更豐富的知識等著大家去學習,希望對大家能夠有所幫助哦。

提交申請后,顧問老師會電話與您溝通安排學習

免費課程推薦 >>
技術文檔推薦 >>
主站蜘蛛池模板: 欧美最猛的24k毛片视频 | 国产精品亚洲高清一区二区 | 久久草在线视频国产一 | 日韩天堂在线 | 亚洲免费在线视频 | 玖玖在线免费视频 | 国产视频在线一区 | 国产 麻豆 欧美亚洲综合久久 | 亚洲天天做夜夜做天天欢 | 久久精品乱子伦免费 | 国产精品人成人免费国产 | 国产精品国产福利国产秒拍 | 成人久久| 日本美女一区 | 极品俄罗斯性孕妇孕交 | 欧美亚洲网站 | 麻豆国产精品视频 | 亚洲精品美女久久777777 | 黄色在线观看免费 | 成人毛片国产a | 四虎影院在线播放视频 | 午夜毛片 | 亚洲乱码一区二区三区在线观看 | 伊人在综合 | 国产性较精品视频免费 | 成人a毛片视频免费看 | 中文字幕2区 | 伊人丁香狠狠色综合久久 | 曰本女人一级毛片看一级毛 | 九九在线观看精品视频6 | 国产精品亚洲综合一区 | 国产在播放一区 | 在线观看人成网站深夜免费 | 久久伊人中文字幕有码 | 91探花视频在线观看 | 天天做天天爱天天爽 | 涩涩www在线观看免费高清 | 国产精品人人 | 亚洲激情一区 | 国产欧美日韩看片片在线人成 | 久久欧美精品 |