2. sharding-jdbc源碼之Configuration

阿飛Javaer,轉載請註明原創出處,謝謝!

上篇文章sharding-jdbc源碼之數據源介紹了通過Java硬編碼創建ShardingDataSource。這篇文章通過分析sharding-jdbc-config-parent模塊,學習如何通過YAML配置或者spring配置創建ShardingDataSourcesharding-jdbc-config-parent模塊包含了三個子模塊,關係如下圖所示:
sharding-jdbc-config-parent
|__sharding-jdbc-config-common
|__sharding-jdbc-config-spring
|__sharding-jdbc-config-yaml

無論是yaml方式還是spring方式配置ShardingDataSource,最終都會轉化爲sharding-jdbc-config-common中定義的對象;接下來對兩種方式進行源碼分析:

YAML配置

可以通過sharding-jdbc-example-config-yaml模塊中YamlWithAssignedDataSourceMain.java進行debug;通過YamlWithAssignedDataSourceMain.java源碼可知,yaml方式配置數據庫的核心源碼在YamlShardingDataSource中;

public final class YamlWithAssignedDataSourceMain {

    public static void main(final String[] args) throws Exception {
        YamlShardingDataSource dataSource =  new YamlShardingDataSource(
            new File(YamlWithAssignedDataSourceMain.class.getResource("/META-INF/withAssignedDataSource.yaml").getFile()));
        ... ...
    }
}

說明:withAssignedDataSource.yaml的內容請自行查看源碼;

com.dangdang.ddframe.rdb.sharding.config.yaml.api.YamlShardingDataSource.java位於sharding-jdbc-config-yaml模塊中,核心源碼如下:

public class YamlShardingDataSource extends ShardingDataSource {

    // 通過yaml文件配置數據源的方式
    public YamlShardingDataSource(final File yamlFile) throws IOException, SQLException {
        // unmarshal(yamlFile)方法是解析yaml文件的核心源碼,其作用是將yaml文件解釋爲YamlConfig(父類是ShardingRuleConfig)
        super(new ShardingRuleBuilder(yamlFile.getName(), unmarshal(yamlFile)).build(), unmarshal(yamlFile).getProps());
    }

    ... ...

    private static YamlConfig unmarshal(final File yamlFile) throws IOException {
        try (
                FileInputStream fileInputStream = new FileInputStream(yamlFile);
                InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8")
        ) {
            // yaml解釋依賴第三方組件:org.yaml.snakeyaml; config-all.yaml內容解釋成ShardingRuleConfig
            return new Yaml(new Constructor(YamlConfig.class)).loadAs(inputStreamReader, YamlConfig.class);
        }
    }

    private static YamlConfig unmarshal(final byte[] yamlByteArray) throws IOException {
        return new Yaml(new Constructor(YamlConfig.class)).loadAs(new ByteArrayInputStream(yamlByteArray), YamlConfig.class);

    }
}

通過這段源碼可知,接下來就會調用ShardingDataSource的構造方法,因爲YamlShardingDataSource構造方法中調用了super(),而且YamlShardingDataSource繼承自ShardingDataSource;

spring配置

可以通過sharding-jdbc-example-config-spring模塊中SpringNamespaceWithAssignedDataSourceMain.java進行debug;其源碼就是加載applicationContextWithAssignedDataSource.xml文件,該文件中<rdb>節點即sharding-jdbc定義節點部分的內容如下:

<rdb:strategy id="databaseStrategy" sharding-columns="user_id" algorithm-expression="dbtbl_${user_id.longValue() % 2}"/>
<rdb:strategy id="orderTableStrategy" sharding-columns="order_id" algorithm-expression="t_order_${order_id.longValue() % 4}"/>
<rdb:strategy id="orderItemTableStrategy" sharding-columns="order_id" algorithm-class="com.dangdang.ddframe.rdb.sharding.example.config.spring.algorithm.SingleKeyModuloTableShardingAlgorithm"/>
<rdb:data-source id="shardingDataSource">
    <rdb:sharding-rule data-sources="dbtbl_0,dbtbl_1,dbtbl_config">
        <rdb:table-rules>
            <rdb:table-rule logic-table="t_config" actual-tables="dbtbl_config.t_config"/>
            <rdb:table-rule logic-table="t_order" actual-tables="dbtbl_${0..1}.t_order_${0..3}" 
            database-strategy="databaseStrategy" table-strategy="orderTableStrategy"/>
            <rdb:table-rule logic-table="t_order_item" actual-tables="
            dbtbl_${0..1}.t_order_item_0,
            dbtbl_${0..1}.t_order_item_1,
            dbtbl_${0..1}.t_order_item_2,
            dbtbl_${0..1}.t_order_item_3" 
            database-strategy="databaseStrategy" table-strategy="orderItemTableStrategy"/>
        </rdb:table-rules>
        <rdb:default-database-strategy sharding-columns="none" algorithm-class="com.dangdang.ddframe.rdb.sharding.api.strategy.database.NoneDatabaseShardingAlgorithm"/>
        <rdb:default-table-strategy sharding-columns="none" algorithm-class="com.dangdang.ddframe.rdb.sharding.api.strategy.table.NoneTableShardingAlgorithm"/>
    </rdb:sharding-rule>
</rdb:data-source>

配置文件基於inline表達式,部分內容解讀如下:
* 邏輯表表名爲t_order,其實際表是dbtbl_${0..1}.t_order_${0..3}
* t_order表的分表策略,通過table-strategy指定,即orderTableStrategy–根據order_id列的值對4取模;
* t_order_item分表策略,通過table-strategy指定,即orderItemTableStrategy–實現算法就是根據根據order_id列對4取模;具體實現請參考SingleKeyModuloTableShardingAlgorithm;
* 數據庫的分庫策略,通過database-strategy指定,即databaseStrategy–根據user_id列的值對2取模;
* 默認數據庫和表的分庫分表策略:不需要根據任何列水平切分(sharding-columns=”none”);

通過sharding-jdbc-config-spring模塊中spring.handlers裏的配置http://www.dangdang.com/schema/ddframe/rdb=com.dangdang.ddframe.rdb.sharding.spring.namespace.handler.ShardingJdbcNamespaceHandler
可知,spring.xml中的<rdb>節點由ShardingJdbcNamespaceHandler進行解析,核心源碼如下:

public final class ShardingJdbcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 註冊<rdb:strategy>節點的解析器爲ShardingJdbcStrategyBeanDefinitionParser
        registerBeanDefinitionParser("strategy", new ShardingJdbcStrategyBeanDefinitionParser());
        // 註冊<rdb:data-source>節點的解析器爲ShardingJdbcDataSourceBeanDefinitionParser
        registerBeanDefinitionParser("data-source", new ShardingJdbcDataSourceBeanDefinitionParser());
        // 註冊<rdb:master-slave-data-source>節點的解析器爲MasterSlaveDataSourceBeanDefinitionParser
        registerBeanDefinitionParser("master-slave-data-source", new MasterSlaveDataSourceBeanDefinitionParser());
    }
}

spring.xml中data-source節點剖析:
根據上面ShardingJdbcNamespaceHandler裏的源碼可知,<rdb:data-source>節點由ShardingJdbcDataSourceBeanDefinitionParser解析,核心源碼如下;

// 自定義Parser一定要實現org.springframework.beans.factory.xml.AbstractBeanDefinitionParser才能作爲spring.xml中節點中的解析器,這是spring的約定;
public class ShardingJdbcDataSourceBeanDefinitionParser extends AbstractBeanDefinitionParser {

    @Override
    // 這是解析入口,這時element是spring.xml中`<rdb:data-source>`節點;
    protected AbstractBeanDefinition parseInternal(final Element element, final ParserContext parserContext) {
        // 準備把`<rdb:data-source>`節點中數據解析成爲SpringShardingDataSource
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(SpringShardingDataSource.class);
        // 解析成SpringShardingDataSource且增加兩個構造方法中屬性的值(由後面SpringShardingDataSource.java定義可知,構造方法需要兩個參數:一個是ShardingRuleConfig類型,一個是Properties類型)
        factory.addConstructorArgValue(parseShardingRuleConfig(element, parserContext));
        factory.addConstructorArgValue(parseProperties(element, parserContext));
        factory.setDestroyMethodName("close");
        return factory.getBeanDefinition();
    }

    // 這是解析SpringShardingDataSource構造方法中ShardingRuleConfig類型參數的值
    private BeanDefinition parseShardingRuleConfig(final Element element, final ParserContext parserContext) {
        // 先獲取<rdb:sharding-rule>節點
        Element shardingRuleElement = DomUtils.getChildElementByTagName(element, ShardingJdbcDataSourceBeanDefinitionParserTag.SHARDING_RULE_CONFIG_TAG);
        // 將這個節點內容解析成ShardingRuleConfig(參數後面的ShardingRuleConfig.java定義)
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ShardingRuleConfig.class);
        // ShardingRuleConfig中dataSource屬性賦值
        factory.addPropertyValue("dataSource", parseDataSources(shardingRuleElement, parserContext));
        // ShardingRuleConfig中defaultDataSourceName屬性賦值
        parseDefaultDataSource(factory, shardingRuleElement);
        // ShardingRuleConfig中tables屬性賦值
        factory.addPropertyValue("tables", parseTableRulesConfig(shardingRuleElement));
        // ShardingRuleConfig中bindingTables屬性賦值
        factory.addPropertyValue("bindingTables", parseBindingTablesConfig(shardingRuleElement));
        // ShardingRuleConfig中defaultDatabaseStrategy屬性賦值
        factory.addPropertyValue("defaultDatabaseStrategy", parseDefaultDatabaseStrategyConfig(shardingRuleElement));
        // ShardingRuleConfig中defaultTableStrategy屬性賦值
        factory.addPropertyValue("defaultTableStrategy", parseDefaultTableStrategyConfig(shardingRuleElement));
        // ShardingRuleConfig中keyGeneratorClass屬性賦值
        parseKeyGenerator(factory, shardingRuleElement);
        return factory.getBeanDefinition();
    }

SpringShardingDataSource.java定義:

public class SpringShardingDataSource extends ShardingDataSource {
    // parseInternal()中解析完spring.xml中的<rdb:data-source>節點後,調用這個構造方法
    public SpringShardingDataSource(final ShardingRuleConfig shardingRuleConfig, final Properties props) throws SQLException {
        super(new ShardingRuleBuilder(shardingRuleConfig).build(), props);
    }
}

ShardingDataSource剖析

無論是yaml配置還是spring.xml配置,最終都會調用ShardingDataSource裏的構造方法,接下來對其進行分析;

public class ShardingDataSource extends AbstractDataSourceAdapter implements AutoCloseable {
    public ShardingDataSource(final ShardingRule shardingRule, final Properties props) throws SQLException {
        super(shardingRule.getDataSourceRule().getDataSources());
        shardingProperties = new ShardingProperties(null == props ? new Properties() : props);
        // 默認值是CPU核心數
        int executorSize = shardingProperties.getValue(ShardingPropertiesConstant.EXECUTOR_SIZE);
        // ExecutorEngine的構造依賴於google-guava的MoreExecutors
        executorEngine = new ExecutorEngine(executorSize);
        // 是否有配置文件配置了sql_show
        boolean showSQL = shardingProperties.getValue(ShardingPropertiesConstant.SQL_SHOW);
        shardingContext = new ShardingContext(shardingRule, getDatabaseType(), executorEngine, showSQL);
    }
    ...
}

通過該構造方法的源碼可知: 申明的數據源集合,例如spring.xml中<rdb:sharding-rule data-sources="dbtbl_0,dbtbl_1,dbtbl_config"> ,所有數據源必須是相同的數據庫類型;要麼全是MySQL,要麼全是Oracle;否則拋出異常:Database type inconsistent with ‘%s’ and ‘%s’;其數據庫類型根據connection.getMetaData().getDatabaseProductName()得到;

另外,通過這段源碼可知,可配置的屬性有sql_showexecutor.size,定義在ShardingPropertiesConstant.java中:
1. 兩個屬性在spring.xml中的配置參考:

<rdb:data-source id="shardingDataSource">
    <rdb:sharding-rule  data-sources="dbtbl_0,dbtbl_1,dbtbl_config">
        ... ...
    </rdb:sharding-rule>
    <rdb:props>
        <prop key="sql.show">true</prop>
        <prop key="executor.size">2</prop>
    </rdb:props>
</rdb:data-source>
  1. 兩個屬性在yaml文件中的配置參考:
props:
  sql.show: false
  executor.size: 4

附ShardingRuleConfig.java定義:

@Getter
@Setter
public class ShardingRuleConfig {

    private Map<String, DataSource> dataSource = new HashMap<>();

    private String defaultDataSourceName;

    private Map<String, TableRuleConfig> tables = new HashMap<>();

    private List<BindingTableRuleConfig> bindingTables = new ArrayList<>();

    private StrategyConfig defaultDatabaseStrategy;

    private StrategyConfig defaultTableStrategy;

    private String keyGeneratorClass;
}

Debug

以spring配置數據源的方式進行debug,Main方法爲SpringNamespaceWithAssignedDataSourceMain.java,debug之前,需要執行sharding-jdbc-example-config-spring模塊中的all_schema.sql腳本;

YAML解析&lombok實戰

通過上面對sharding-jdbc源碼的分析,發現sharding-jdbc支持yaml格式配置,且大量使用lombok簡化源碼,接下來簡單實踐yaml格式文件如何解析,以及lombok如何使用;

假設需要解析的yaml文件內容如下:

rdb:
  oracle:
    username: OracleUse&1
    password: OrcUse*&1
    driverClassName: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@192.168.0.2:1521:xe
  mysql:
    username: MySQLUse&1
    password: MyUse*&1
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.0.1:3306/financials_rules?autoCommit=true

nosql:
  mongodb:
    username: MongoUse&1
    password: MgoUse*&1
  redis:
    password: RdsUse*&1

newsql:

解析yaml文件的核心代碼如下:

public class DataSourceTest {

    /**
     * 這個yaml文件要放在resources目錄下
     */
    private static final String YAML_FILE_PATH = "datasource.yaml";

    public static void main(String[] args) throws Exception {
        System.out.println(JSON.toJSONString(
                unmarshal(DataSourceTest.class.getClassLoader().getResourceAsStream(YAML_FILE_PATH))));
    }

    private static DataSourceConfig unmarshal(final InputStream is) throws IOException {
        try (
                InputStreamReader inputStreamReader = new InputStreamReader(is, "UTF-8")
        ) {
            return new Yaml(new Constructor(DataSourceConfig.class)).loadAs(inputStreamReader, DataSourceConfig.class);
        }
    }

}

DataSourceConfig.java源碼如下:

@Getter
@Setter
public class DataSourceConfig {

    private Map<String, DataSourceItemConfig> rdb;

    private Map<String, DataSourceItemConfig> nosql;

    private Map<String, DataSourceItemConfig> newsql;

}

DataSourceItemConfig.java源碼如下:

@Getter
@Setter
public class DataSourceItemConfig {

    private String username;

    private String password;

    private String driverClassName;

    private String url;

}

最終輸出結果爲:

{
    "nosql": {
        "mongodb": {
            "password": "MgoUse*&1",
            "username": "MongoUse&1"
        },
        "redis": {
            "password": "RdsUse*&1"
        }
    },
    "rdb": {
        "oracle": {
            "driverClassName": "oracle.jdbc.OracleDriver",
            "password": "OrcUse*&1",
            "url": "jdbc:oracle:thin:@192.168.0.2:1521:xe",
            "username": "OracleUse&1"
        },
        "mysql": {
            "driverClassName": "com.mysql.jdbc.Driver",
            "password": "MyUse*&1",
            "url": "jdbc:mysql://192.168.0.1:3306/financials_rules?autoCommit=true",
            "username": "MySQLUse&1"
        }
    }
}

YAML&lombok Maven座標

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.16</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.4</version>
    <scope>provided</scope>
</dependency>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章