阿飛Javaer,轉載請註明原創出處,謝謝!
上篇文章sharding-jdbc源碼之數據源介紹了通過Java硬編碼創建ShardingDataSource。這篇文章通過分析sharding-jdbc-config-parent
模塊,學習如何通過YAML配置或者spring配置創建ShardingDataSource;sharding-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_show
和executor.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>
- 兩個屬性在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>