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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容