Sharding-JDBC(三)3.1.0版本實踐

目錄

  一、Sharding-JDBC依賴

  二、代碼實踐

  三、源碼分析

 

在上一篇博文中,介紹了Sharding-JDBC的分片策略、分片鍵和分片算法的基本概念,以及2.0.3版本可以支持和無法支持的使用場景。

可以支持的場景:支持對SQL語句中的=、IN和BETWEEN AND的分片操作,但前提是分片鍵必須存在於SQL和數據表結構中。

無法支持的場景:分片鍵不存在於SQL和數據表結構中,即基於暗示(Hint)的數據分片操作(2.0.3版本的問題)。

無可厚非,缺少了Hint分片策略的支持,Sharding-JDBC 2.0.3版本的使用場景就非常受限了,但值得慶幸的是,此問題在3.x版本進行了修復(這裏可以有掌聲!),接下來的代碼皆基於3.1.0版本。

一、Sharding-JDBC依賴

複製代碼

<!-- sharding-jdbc-core -->
<dependency>
    <groupId>io.shardingsphere</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>3.1.0</version>
</dependency>
<!-- sharding-jdbc-spring-namespace -->
<dependency>
    <groupId>io.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-namespace</artifactId>
    <version>3.1.0</version>
</dependency>

複製代碼

 和2.0.3版本相比,依賴的名稱有所改變,不要搞錯了哦。

二、代碼實踐

業務背景就不再介紹了,不瞭解可移步至Sharding-JDBC(二)2.0.3版本實踐

如下代碼配置了標準分片策略中的精確分片算法PreciseShardingAlgorithm和Hint分片算法HintShardingAlgorithm。

XML配置:

複製代碼

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sharding="http://shardingsphere.io/schema/shardingsphere/sharding"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://shardingsphere.io/schema/shardingsphere/sharding
                           http://shardingsphere.io/schema/shardingsphere/sharding/sharding.xsd">

    <!-- 標準分片策略 -->
    <sharding:standard-strategy id="settlementTableShardingStandardStrategy" sharding-column="pay_serial_number"
                                precise-algorithm-ref="preciseTableShardingAlgorithm"/>

    <!-- 基於暗示(Hint)的分片策略 -->
    <sharding:hint-strategy id="settlementHintTableShardingStrategy" algorithm-ref="hintTableShardingAlgorithm"/>
    <sharding:hint-strategy id="settlementHintDatabaseShardingStrategy" algorithm-ref="hintDatabaseShardingAlgorithm"/>

    <sharding:data-source id="shardingDataSource">
        <sharding:sharding-rule data-source-names="dataSource">
            <sharding:table-rules>
                <sharding:table-rule logic-table="settlement"
                                     table-strategy-ref="settlementTableShardingStandardStrategy"/>
                <!-- logic-table參數的大小寫必須和SettlementMapper.xml中selectByExample方法的表名大小一致!!! -->
                <!-- logic-table必須和org.cellphone.finance.repo.SettlementRepository.querySettlements中的logicTable及SQL中的表名一致,否則無法找到分片策略 -->
                <!-- 邏輯表名,不需要和真實表名一致 -->
                <sharding:table-rule logic-table="settlement_hint"
                                     database-strategy-ref="settlementHintDatabaseShardingStrategy"
                                     table-strategy-ref="settlementHintTableShardingStrategy"/>
            </sharding:table-rules>
        </sharding:sharding-rule>
        <sharding:props>
            <prop key="sql.show">true</prop>
        </sharding:props>
    </sharding:data-source>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="shardingDataSource"/>
    </bean>
    <tx:annotation-driven/>

</beans>

複製代碼

精確分片算法:

複製代碼

package org.cellphone.finance.repo.sharding;

import com.google.common.collect.Lists;
import io.shardingsphere.api.algorithm.sharding.PreciseShardingValue;
import io.shardingsphere.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
import org.apache.commons.collections.CollectionUtils;
import org.cellphone.common.constant.CommonConst;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * 精確分片算法,屬於標準分片算法,用於處理=和IN的分片
 * <p>
 * 使用精確分片算法的前提:分片字段必須存在與SQL中、數據庫表結構中,否則無法使用精確分片算法
 * <p>
 * 此分片算法應用於SETTLEMENT數據表,這裏是按天分表
 * <p>
 * 特別說明:Sharding Jdbc版本:3.1.0
 * <p>
 * Created by on 2018/4/9.
 */
@Component("preciseTableShardingAlgorithm")
public class PreciseTableShardingAlgorithm implements PreciseShardingAlgorithm<String> {

    /**
     * 精確分片算法
     *
     * @param availableTargetNames 目標數據源名稱或數據表名稱,注意:是邏輯數據源名或邏輯數據表名,來自SQL
     * @param shardingValue        分片值,來自SQL中分片字段對應的值
     * @return 真實數據源名稱或數據表名稱
     */
    @Override
    public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<String> shardingValue) {
        // 默認數據表名稱,有可能數據庫中不存在這張表
        String tableName = "settlement";

        // 邏輯表名爲空,返回默認表名
        if (CollectionUtils.isEmpty(availableTargetNames))
            return tableName;

        // availableTargetNames來自SQL,只有一個元素
        tableName = Lists.newArrayList(availableTargetNames).get(0);

        String paySerialNumber = shardingValue.getValue();
        String suffix = paySerialNumber.substring(5, 13);
        return tableName + CommonConst.UNDERLINE + suffix;
    }
}

複製代碼

Hint數據源分片算法:

複製代碼

package org.cellphone.finance.repo.sharding;

import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.api.algorithm.sharding.hint.HintShardingAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * Sharding Jdbc基於暗示(Hint)的數據分片算法
 *
 * 使用Sharding Jdbc 3.x版本時,此數據源分片算法這個一定要有!!!
 * 否則無法正常使用org.cellphone.finance.repo.sharding.HintTableShardingAlgorithm算法
 * <p>
 * Created by on 2019/4/25.
 */
@Component("hintDatabaseShardingAlgorithm")
public class HintDatabaseShardingAlgorithm implements HintShardingAlgorithm {

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, ShardingValue shardingValue) {
        return availableTargetNames;
    }
}

複製代碼

Hint數據表分片算法:

複製代碼

package org.cellphone.finance.repo.sharding;

import com.google.common.collect.Lists;
import io.shardingsphere.api.algorithm.sharding.ListShardingValue;
import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.api.algorithm.sharding.hint.HintShardingAlgorithm;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.cellphone.common.constant.CommonConst;
import org.cellphone.common.constant.DateConst;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.*;

/**
 * Sharding Jdbc基於暗示(Hint)的數據分片算法
 * 版本:Sharding Jdbc 3.1.0
 *
 * 官方介紹(2.x版本):http://shardingsphere.apache.org/document/legacy/2.x/cn/02-guide/hint-sharding-value/
 * 官方介紹(4.x版本):https://shardingsphere.apache.org/document/current/cn/manual/sharding-jdbc/usage/hint/
 * <p>
 * <p>
 * <p>
 * <p>
 * 使用此算法的背景如下:
 * 1. SETTLEMENT(支付表)是按時間維度進行分表,該時間取自PAY_SERIAL_NUMBER中的時間數據,非表中的時間字段;
 * <p>
 * 2. 使用用戶手機號此類條件查詢數據時,請求參數除傳入業務參數外,
 *      還需傳入時間段(即分片字段,例:startTime和endTime)以確定分表範圍;
 *      但是!!!startTime和endTime(即分片字段)不存在SQL中、數據庫表結構中,而存在於外部業務邏輯
 * <p>
 * <p>
 * <p>
 * 因此,第2點導致無法直接使用Sharding Jdbc的PreciseShardingAlgorithm(精確分片算法)或RangeShardingAlgorithm(範圍分片算法),
 * 只能使用HintShardingAlgorithm(基於暗示的數據分片算法),該算法的使用場景如下:
 * 1. 分片字段不存在SQL中、數據庫表結構中,而存在於外部業務邏輯;
 * <p>
 * 2. 強制在主庫進行某些數據操作。
 * <p>
 * <p>
 * Created by on 2019/4/25.
 */
@Component("hintTableShardingAlgorithm")
public class HintTableShardingAlgorithm implements HintShardingAlgorithm {

    /**
     * 分片算法
     *
     * @param availableTargetNames 邏輯數據庫名稱或邏輯數據表名稱
     * @param shardingValue        用來確定分表的參數
     * @return 實際數據表名稱列表,SQL實際操作的數據表
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, ShardingValue shardingValue) {

        String realTableName = StringUtils.EMPTY;
        for (String each : availableTargetNames) {
            if (StringUtils.isNotBlank(each)) {
                // 基於hint的邏輯表名:settlement_hint
                realTableName = each.replace("_hint", StringUtils.EMPTY);
                break;
            }
        }

        List<String> tables = new ArrayList<>();

        ListShardingValue<String> listShardingValue = (ListShardingValue<String>) shardingValue;
        List<String> list = Lists.newArrayList(listShardingValue.getValues());

        // 缺少確定分表的參數,無法確定具體分表,直接返回真實表名稱
        if (CollectionUtils.isEmpty(list)) {
            tables.add(realTableName);
            return tables;
        }

        // 拆分分表參數,此參數值來自:com.fcbox.manage.core.repo.FcBoxPostRepository.queryFcBoxPosts()
        String[] queryTime = list.get(0).split(CommonConst.UNDERLINE);
        Date startTime, endTime;
        try {
            startTime = DateUtils.parseDate(queryTime[0], DateConst.DATE_FORMAT_NORMAL);
            endTime   = DateUtils.parseDate(queryTime[1], DateConst.DATE_FORMAT_NORMAL);
        } catch (ParseException e) {
            // 分表參數解析錯誤,無法確定具體分表,直接返回真實表名稱
            tables.add(realTableName);
            return tables;
        }

        Calendar calendar = Calendar.getInstance();
        // 組織startTime和endTime時段範圍內的分表
        while (startTime.getTime() <= endTime.getTime()) {
            tables.add(realTableName + CommonConst.UNDERLINE + DateFormatUtils.format(startTime, DateConst.DATE_FORMAT_YYYY_MM_DD));
            calendar.setTime(startTime);
            calendar.add(Calendar.DATE, 1);
            startTime = calendar.getTime();
        }

        return tables;
    }
}

複製代碼

與Hint分片算法對應的Java查詢方法 settlementMapper.selectByExample(example):

複製代碼

    public List<Settlement> querySettlements(SettlementExample example, String startTime, String endTime) {
        // 組織查詢時間,傳入org.cellphone.finance.repo.sharding.HintTableShardingAlgorithm分片算法中以確認具體分表
        String queryTime = startTime + CommonConst.UNDERLINE + endTime;

        // 獲取HintManager
        HintManager hintManager = HintManager.getInstance();
        /*
         * 添加數據源分片鍵值,使用Sharding Jdbc 3.x版本一定要添加數據源分片鍵值,否則無法使用HintTableShardingAlgorithm分片算法
         * 若無分庫,addDatabaseShardingValue方法的value字段隨意填充
         * 若有分庫,addDatabaseShardingValue方法的value字段填充實際參數值
         */
        hintManager.addDatabaseShardingValue("settlement_hint", StringUtils.EMPTY);
        // 添加數據表分片鍵值
        hintManager.addTableShardingValue("settlement_hint", queryTime);
        List<Settlement> settlements = settlementMapper.selectByExample(example);
        // 清除分片鍵值
        hintManager.close();
        return settlements;
    }

複製代碼

以及該查詢方法對應的SQL語句:

select * from settlement_hint t where t.pay_serial_number = ?

 單元測試代碼:

複製代碼

@Test
public void test003QuerySettlements() throws ParseException {
    String startTime = "2018-04-03 00:00:00", endTime = "2018-04-05 00:00:00";

    SettlementExample example = new SettlementExample();
    SettlementExample.Criteria criteria = example.createCriteria();
    criteria.andPaySerialNumberEqualTo(paySerialNumber);

    List<Settlement> settlements = repository.querySettlements(example, startTime, endTime);

    Assert.assertEquals("136********", settlements.get(0).getUserMobile());
}

複製代碼

三、源碼分析

和2.0.3版本相比,3.1.0版本的路由入口變成了 io.shardingsphere.core.routing.type.standard.StandardRoutingEngine#route() ,但基本上區別不大,僅僅是多了一步需要確定路由的真實數據源,儘管數據源只有一個,也需要顯式配置數據源路由算法。代碼中標註了註釋的部分都是路由代碼比較核心的部分。

複製代碼

/*
 * Copyright 2016-2018 shardingsphere.io.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */

package io.shardingsphere.core.routing.type.standard;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.core.hint.HintManagerHolder;
import io.shardingsphere.core.optimizer.condition.ShardingCondition;
import io.shardingsphere.core.optimizer.condition.ShardingConditions;
import io.shardingsphere.core.optimizer.insert.InsertShardingCondition;
import io.shardingsphere.core.routing.strategy.ShardingStrategy;
import io.shardingsphere.core.routing.strategy.hint.HintShardingStrategy;
import io.shardingsphere.core.routing.type.RoutingEngine;
import io.shardingsphere.core.routing.type.RoutingResult;
import io.shardingsphere.core.routing.type.RoutingTable;
import io.shardingsphere.core.routing.type.TableUnit;
import io.shardingsphere.core.rule.BindingTableRule;
import io.shardingsphere.core.rule.DataNode;
import io.shardingsphere.core.rule.ShardingRule;
import io.shardingsphere.core.rule.TableRule;
import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;

/**
 * Standard routing engine.
 *
 * @author zhangliang
 * @author maxiaoguang
 * @author panjuan
 */
@RequiredArgsConstructor
public final class StandardRoutingEngine implements RoutingEngine {

    private final ShardingRule shardingRule;

    private final String logicTableName;

    private final ShardingConditions shardingConditions;

    /**
     * 相比2.0.3版本,精簡成了1行代碼
     *
     * @return 路由結果
     */
    @Override
    public RoutingResult route() {
        return generateRoutingResult(getDataNodes(shardingRule.getTableRuleByLogicTableName(logicTableName)));
    }

    private RoutingResult generateRoutingResult(final Collection<DataNode> routedDataNodes) {
        RoutingResult result = new RoutingResult();
        for (DataNode each : routedDataNodes) {
            TableUnit tableUnit = new TableUnit(each.getDataSourceName());
            tableUnit.getRoutingTables().add(new RoutingTable(logicTableName, each.getTableName()));
            result.getTableUnits().getTableUnits().add(tableUnit);
        }
        return result;
    }

    /**
     * 獲取數據節點,即真實表
     *
     * @param tableRule 從XML讀取的table rule
     * @return 數據節點列表
     */
    private Collection<DataNode> getDataNodes(final TableRule tableRule) {
        // 判斷是否是通過Hint分片策略進行路由
        if (isRoutingByHint(tableRule)) {
            return routeByHint(tableRule);
        }
        if (isRoutingByShardingConditions(tableRule)) {
            return routeByShardingConditions(tableRule);
        }
        return routeByMixedConditions(tableRule);
    }

    /**
     * 判斷是否是通過Hint分片策略進行路由
     * <p>
     * 數據源分片策略和數據表分片策略都必須是HintShardingStrategy,這意味者必須顯式配置數據源Hint分片策略和數據表Hint分片策略
     *
     * @param tableRule 從XML讀取的table rule
     * @return
     */
    private boolean isRoutingByHint(final TableRule tableRule) {
        return shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy && shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy;
    }

    /**
     * 通過Hint分片策略進行路由
     *
     * @param tableRule
     * @return
     */
    private Collection<DataNode> routeByHint(final TableRule tableRule) {
        return route(tableRule, getDatabaseShardingValuesFromHint(), getTableShardingValuesFromHint());
    }

    private boolean isRoutingByShardingConditions(final TableRule tableRule) {
        return !(shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy || shardingRule.getTableShardingStrategy(tableRule) instanceof HintShardingStrategy);
    }

    private Collection<DataNode> routeByShardingConditions(final TableRule tableRule) {
        return shardingConditions.getShardingConditions().isEmpty() ? route(tableRule, Collections.<ShardingValue>emptyList(), Collections.<ShardingValue>emptyList())
                : routeByShardingConditionsWithCondition(tableRule);
    }

    private Collection<DataNode> routeByShardingConditionsWithCondition(final TableRule tableRule) {
        Collection<DataNode> result = new LinkedList<>();
        for (ShardingCondition each : shardingConditions.getShardingConditions()) {
            Collection<DataNode> dataNodes = route(tableRule, getShardingValuesFromShardingConditions(shardingRule.getDatabaseShardingStrategy(tableRule).getShardingColumns(), each),
                    getShardingValuesFromShardingConditions(shardingRule.getTableShardingStrategy(tableRule).getShardingColumns(), each));
            reviseShardingConditions(each, dataNodes);
            result.addAll(dataNodes);
        }
        return result;
    }

    private Collection<DataNode> routeByMixedConditions(final TableRule tableRule) {
        return shardingConditions.getShardingConditions().isEmpty() ? routeByMixedConditionsWithHint(tableRule) : routeByMixedConditionsWithCondition(tableRule);
    }

    private Collection<DataNode> routeByMixedConditionsWithCondition(final TableRule tableRule) {
        Collection<DataNode> result = new LinkedList<>();
        for (ShardingCondition each : shardingConditions.getShardingConditions()) {
            Collection<DataNode> dataNodes = route(tableRule, getDatabaseShardingValues(tableRule, each), getTableShardingValues(tableRule, each));
            reviseShardingConditions(each, dataNodes);
            result.addAll(dataNodes);
        }
        return result;
    }

    private Collection<DataNode> routeByMixedConditionsWithHint(final TableRule tableRule) {
        if (shardingRule.getDatabaseShardingStrategy(tableRule) instanceof HintShardingStrategy) {
            return route(tableRule, getDatabaseShardingValuesFromHint(), Collections.<ShardingValue>emptyList());
        }
        return route(tableRule, Collections.<ShardingValue>emptyList(), getTableShardingValuesFromHint());
    }

    private List<ShardingValue> getDatabaseShardingValues(final TableRule tableRule, final ShardingCondition shardingCondition) {
        ShardingStrategy dataBaseShardingStrategy = shardingRule.getDatabaseShardingStrategy(tableRule);
        return isGettingShardingValuesFromHint(dataBaseShardingStrategy)
                ? getDatabaseShardingValuesFromHint() : getShardingValuesFromShardingConditions(dataBaseShardingStrategy.getShardingColumns(), shardingCondition);
    }

    private List<ShardingValue> getTableShardingValues(final TableRule tableRule, final ShardingCondition shardingCondition) {
        ShardingStrategy tableShardingStrategy = shardingRule.getTableShardingStrategy(tableRule);
        return isGettingShardingValuesFromHint(tableShardingStrategy)
                ? getTableShardingValuesFromHint() : getShardingValuesFromShardingConditions(tableShardingStrategy.getShardingColumns(), shardingCondition);
    }

    private boolean isGettingShardingValuesFromHint(final ShardingStrategy shardingStrategy) {
        return shardingStrategy instanceof HintShardingStrategy;
    }

    /**
     * 從HintManagerHolder中獲取數據源分片值
     *
     * @return 數據源分片值列表
     */
    private List<ShardingValue> getDatabaseShardingValuesFromHint() {
        // getDatabaseShardingValue方法實現有點噁心,不兼容大小寫...
        Optional<ShardingValue> shardingValueOptional = HintManagerHolder.getDatabaseShardingValue(logicTableName);
        return shardingValueOptional.isPresent() ? Collections.singletonList(shardingValueOptional.get()) : Collections.<ShardingValue>emptyList();
    }

    private List<ShardingValue> getTableShardingValuesFromHint() {
        // getTableShardingValue方法實現有點噁心,不兼容大小寫...
        Optional<ShardingValue> shardingValueOptional = HintManagerHolder.getTableShardingValue(logicTableName);
        return shardingValueOptional.isPresent() ? Collections.singletonList(shardingValueOptional.get()) : Collections.<ShardingValue>emptyList();
    }

    private List<ShardingValue> getShardingValuesFromShardingConditions(final Collection<String> shardingColumns, final ShardingCondition shardingCondition) {
        List<ShardingValue> result = new ArrayList<>(shardingColumns.size());
        for (ShardingValue each : shardingCondition.getShardingValues()) {
            Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(logicTableName);
            if ((logicTableName.equals(each.getLogicTableName()) || bindingTableRule.isPresent() && bindingTableRule.get().hasLogicTable(logicTableName))
                    && shardingColumns.contains(each.getColumnName())) {
                result.add(each);
            }
        }
        return result;
    }

    /**
     * 路由,獲取真實表列表
     *
     * @param tableRule              從XML讀取的table rule
     * @param databaseShardingValues 數據源分片值
     * @param tableShardingValues    數據表分片值
     * @return 真實表列表
     */
    private Collection<DataNode> route(final TableRule tableRule, final List<ShardingValue> databaseShardingValues, final List<ShardingValue> tableShardingValues) {
        Collection<String> routedDataSources = routeDataSources(tableRule, databaseShardingValues);
        Collection<DataNode> result = new LinkedList<>();
        for (String each : routedDataSources) {
            result.addAll(routeTables(tableRule, each, tableShardingValues));
        }
        return result;
    }

    /**
     * 路由到真實數據源
     *
     * @param tableRule              從XML讀取的table rule
     * @param databaseShardingValues 數據源分片值
     * @return 真實數據源列表
     */
    private Collection<String> routeDataSources(final TableRule tableRule, final List<ShardingValue> databaseShardingValues) {
        Collection<String> availableTargetDatabases = tableRule.getActualDatasourceNames();
        if (databaseShardingValues.isEmpty()) {
            return availableTargetDatabases;
        }
        Collection<String> result = new LinkedHashSet<>(shardingRule.getDatabaseShardingStrategy(tableRule).doSharding(availableTargetDatabases, databaseShardingValues));
        Preconditions.checkState(!result.isEmpty(), "no database route info");
        return result;
    }

    /**
     * 路由到真實數據表,和2.0.3版本沒啥區別
     *
     * @param tableRule           從XML讀取的table rule
     * @param routedDataSource    已確認好的數據源
     * @param tableShardingValues 數據表分片值
     * @return 真實表列表
     */
    private Collection<DataNode> routeTables(final TableRule tableRule, final String routedDataSource, final List<ShardingValue> tableShardingValues) {
        Collection<String> availableTargetTables = tableRule.getActualTableNames(routedDataSource);
        Collection<String> routedTables = new LinkedHashSet<>(tableShardingValues.isEmpty() ? availableTargetTables
                : shardingRule.getTableShardingStrategy(tableRule).doSharding(availableTargetTables, tableShardingValues));
        Preconditions.checkState(!routedTables.isEmpty(), "no table route info");
        Collection<DataNode> result = new LinkedList<>();
        for (String each : routedTables) {
            result.add(new DataNode(routedDataSource, each));
        }
        return result;
    }

    private void reviseShardingConditions(final ShardingCondition each, final Collection<DataNode> dataNodes) {
        if (each instanceof InsertShardingCondition) {
            ((InsertShardingCondition) each).getDataNodes().addAll(dataNodes);
        }
    }
}

複製代碼

分析到此,Sharding-JDBC 3.1.0版本可以支持分片鍵不存在於SQL中和數據表結構中的使用場景。但3.1.0版本還有一個比較噁心的地方,Sharding-JDBC在初始化時,會連接數據庫獲取數據表的元數據,包括需要水平切分的表和不需水平切分的表。代碼如下:

複製代碼

/*
 * Copyright 2016-2018 shardingsphere.io.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */

package io.shardingsphere.core.metadata.table.executor;

import com.google.common.base.Optional;
import io.shardingsphere.core.exception.ShardingException;
import io.shardingsphere.core.executor.ShardingExecuteEngine;
import io.shardingsphere.core.metadata.datasource.DataSourceMetaData;
import io.shardingsphere.core.metadata.datasource.ShardingDataSourceMetaData;
import io.shardingsphere.core.metadata.table.TableMetaData;
import io.shardingsphere.core.rule.ShardingRule;
import io.shardingsphere.core.rule.TableRule;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

/**
 * Table meta data initializer.
 *
 * @author zhangliang
 */
public final class TableMetaDataInitializer {

    private final ShardingDataSourceMetaData shardingDataSourceMetaData;

    private final TableMetaDataConnectionManager connectionManager;

    private final TableMetaDataLoader tableMetaDataLoader;

    public TableMetaDataInitializer(final ShardingDataSourceMetaData shardingDataSourceMetaData, final ShardingExecuteEngine executeEngine,
                                    final TableMetaDataConnectionManager connectionManager, final int maxConnectionsSizePerQuery, final boolean isCheckingMetaData) {
        this.shardingDataSourceMetaData = shardingDataSourceMetaData;
        this.connectionManager = connectionManager;
        tableMetaDataLoader = new TableMetaDataLoader(shardingDataSourceMetaData, executeEngine, connectionManager, maxConnectionsSizePerQuery, isCheckingMetaData);
    }

    /**
     * Load all table meta data.
     *
     * @param shardingRule sharding rule
     * @return all table meta data
     */
    public Map<String, TableMetaData> load(final ShardingRule shardingRule) {
        Map<String, TableMetaData> result = new HashMap<>();
        try {
            // 加載需要水平切分的表元數據
            result.putAll(loadShardingTables(shardingRule));
            // 加載不需水平切分的表元數據,如果數據庫中表數量很大,這裏耗時很久...
            result.putAll(loadDefaultTables(shardingRule));
        } catch (final SQLException ex) {
            throw new ShardingException(ex);
        }
        return result;
    }

    private Map<String, TableMetaData> loadShardingTables(final ShardingRule shardingRule) throws SQLException {
        Map<String, TableMetaData> result = new HashMap<>(shardingRule.getTableRules().size(), 1);
        for (TableRule each : shardingRule.getTableRules()) {
            result.put(each.getLogicTable(), tableMetaDataLoader.load(each.getLogicTable(), shardingRule));
        }
        return result;
    }

    private Map<String, TableMetaData> loadDefaultTables(final ShardingRule shardingRule) throws SQLException {
        Map<String, TableMetaData> result = new HashMap<>(shardingRule.getTableRules().size(), 1);
        Optional<String> actualDefaultDataSourceName = shardingRule.findActualDefaultDataSourceName();
        if (actualDefaultDataSourceName.isPresent()) {
            for (String each : getAllTableNames(actualDefaultDataSourceName.get())) {
                result.put(each, tableMetaDataLoader.load(each, shardingRule));
            }
        }
        return result;
    }

    /**
     * 連接數據庫,獲取所有表元數據
     *
     * @param dataSourceName 數據源名稱
     * @return 所有數據表元數據列表
     * @throws SQLException
     */
    private Collection<String> getAllTableNames(final String dataSourceName) throws SQLException {
        Collection<String> result = new LinkedHashSet<>();
        DataSourceMetaData dataSourceMetaData = shardingDataSourceMetaData.getActualDataSourceMetaData(dataSourceName);
        String catalog = null == dataSourceMetaData ? null : dataSourceMetaData.getSchemeName();
        try (Connection connection = connectionManager.getConnection(dataSourceName);
             ResultSet resultSet = connection.getMetaData().getTables(catalog, null, null, new String[]{"TABLE"})) {
            while (resultSet.next()) {
                String tableName = resultSet.getString("TABLE_NAME");
                if (!tableName.contains("$")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章