阿飞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
文件,该文件中节点即sharding-jdbc定义节点部分的内容如下:
配置文件基于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中的
节点由ShardingJdbcNamespaceHandler
进行解析,核心源码如下:
public final class ShardingJdbcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 注册节点的解析器为ShardingJdbcStrategyBeanDefinitionParser
registerBeanDefinitionParser("strategy", new ShardingJdbcStrategyBeanDefinitionParser());
// 注册节点的解析器为ShardingJdbcDataSourceBeanDefinitionParser
registerBeanDefinitionParser("data-source", new ShardingJdbcDataSourceBeanDefinitionParser());
// 注册节点的解析器为MasterSlaveDataSourceBeanDefinitionParser
registerBeanDefinitionParser("master-slave-data-source", new MasterSlaveDataSourceBeanDefinitionParser());
}
}
spring.xml中data-source节点剖析:
根据上面ShardingJdbcNamespaceHandler里的源码可知,
节点由ShardingJdbcDataSourceBeanDefinitionParser解析,核心源码如下;
// 自定义Parser一定要实现org.springframework.beans.factory.xml.AbstractBeanDefinitionParser才能作为spring.xml中节点中的解析器,这是spring的约定;
public class ShardingJdbcDataSourceBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
// 这是解析入口,这时element是spring.xml中``节点;
protected AbstractBeanDefinition parseInternal(final Element element, final ParserContext parserContext) {
// 准备把``节点中数据解析成为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) {
// 先获取节点
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中的节点后,调用这个构造方法
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中
,所有数据源必须是相同的数据库类型;要么全是MySQL,要么全是Oracle;否则抛出异常:Database type inconsistent with '%s' and '%s';其数据库类型根据connection.getMetaData().getDatabaseProductName()得到;
另外,通过这段源码可知,可配置的属性有sql_show
和executor.size
,定义在ShardingPropertiesConstant.java
中:
- 两个属性在spring.xml中的配置参考:
... ...
true
2
- 两个属性在yaml文件中的配置参考:
props:
sql.show: false
executor.size: 4
附ShardingRuleConfig.java定义:
@Getter
@Setter
public class ShardingRuleConfig {
private Map dataSource = new HashMap<>();
private String defaultDataSourceName;
private Map tables = new HashMap<>();
private List 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 rdb;
private Map nosql;
private Map 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坐标
org.yaml
snakeyaml
1.16
org.projectlombok
lombok
1.16.4
provided