本文介绍 Sharding-JDBC 数据源分片之使用 Java 配置实现基于原生 JDBC 的范围分片方案。
注意:请先阅读 【Sharding-JDBC 数据源分片:Java 配置实现基于原生 JDBC 的精确分片方案】,本文示例代码在此基础上增量添加。
目录
- 开发环境
- 基础示例
- 总结
开发环境
- Oracle JDK 1.8.0_201
- Apache Maven 3.6.0
- IntelliJ IDEA (Version 2018.3.3)
- MySQL 5.6.38
基础示例
- 定义精确分片算法接口
PreciseShardingAlgorithm
实现。
package tutorial.shardingsphere.jdbc.algorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;
public final class PreciseModuloShardingDatabaseAlgorithm implements PreciseShardingAlgorithm {
@Override
public String doSharding(Collection dataSourceNames, PreciseShardingValue preciseShardingValue) {
for (String dataSourceName : dataSourceNames) {
if (dataSourceName.endsWith(preciseShardingValue.getValue() % 2 + "")) {
return dataSourceName;
}
}
throw new UnsupportedOperationException();
}
}
- 定义范围分片算法接口
RangeShardingAlgorithm
实现。
package tutorial.shardingsphere.jdbc.algorithm;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
public final class RangeModuloShardingDatabaseAlgorithm implements RangeShardingAlgorithm {
@Override
public Collection doSharding(Collection dataSourceNames, RangeShardingValue rangeShardingValue) {
Set result = new LinkedHashSet<>();
if (Range.closed(1, 5).encloses(rangeShardingValue.getValueRange())) {
for (String dataSourceName : dataSourceNames) {
if (dataSourceName.endsWith("0")) {
result.add(dataSourceName);
}
}
} else if (Range.closed(6, 10).encloses(rangeShardingValue.getValueRange())) {
for (String dataSourceName : dataSourceNames) {
if (dataSourceName.endsWith("1")) {
result.add(dataSourceName);
}
}
} else if (Range.closed(1, 10).encloses(rangeShardingValue.getValueRange())) {
result.addAll(dataSourceNames);
} else {
throw new UnsupportedOperationException();
}
return result;
}
}
- 定义获取数据源的工厂类。
package tutorial.shardingsphere.jdbc.util;
import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import tutorial.shardingsphere.jdbc.algorithm.PreciseModuloShardingDatabaseAlgorithm;
import tutorial.shardingsphere.jdbc.algorithm.RangeModuloShardingDatabaseAlgorithm;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class RangeDataSourceFactory {
/**
* 配置数据源映射
*/
private static Map createDataSourceMap() {
Map result = new HashMap<>();
result.put("ds_0", DataSourceUtils.createDataSource("ds_0"));
result.put("ds_1", DataSourceUtils.createDataSource("ds_1"));
return result;
}
public static DataSource getDataSource() throws SQLException {
// 配置数据源映射
Map dataSourceMap = createDataSourceMap();
// 配置表规则
TableRuleConfiguration tableRuleConfiguration = new TableRuleConfiguration("t_order");
tableRuleConfiguration.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
shardingRuleConfiguration.getTableRuleConfigs().add(tableRuleConfiguration);
// 配置默认分库策略
shardingRuleConfiguration.setDefaultDatabaseShardingStrategyConfig(
new StandardShardingStrategyConfiguration("user_id",
new PreciseModuloShardingDatabaseAlgorithm(),
new RangeModuloShardingDatabaseAlgorithm())
);
// 获取数据源对象
return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfiguration, new Properties());
}
}
与【Sharding-JDBC 数据源分片:Java 配置实现基于原生 JDBC 的精确分片方案】 中定义的 DataSourceFactory
唯一区别在于配置的默认分库策略不同,请注意 StandardShardingStrategyConfiguration
构造方法。
- 定义新的
Order
(订单)数据访问实现,继承OrderDaoImpl
,重写select
方法。
package tutorial.shardingsphere.jdbc.dao.impl;
import tutorial.shardingsphere.jdbc.bean.Order;
import javax.sql.DataSource;
import java.util.List;
public class RangeOrderDaoImpl extends OrderDaoImpl {
public RangeOrderDaoImpl(DataSource dataSource) {
super(dataSource);
}
@Override
public List select() {
String sql = "SELECT * FROM t_order WHERE user_id BETWEEN 1 AND 5";
return listOrders(sql);
}
}
- 编写单元测试。
package tutorial.shardingsphere.jdbc;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import tutorial.shardingsphere.jdbc.bean.Order;
import tutorial.shardingsphere.jdbc.dao.IOrderDao;
import tutorial.shardingsphere.jdbc.dao.impl.RangeOrderDaoImpl;
import tutorial.shardingsphere.jdbc.util.RangeDataSourceFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
public class JdbcConfigRangeShardingDatabaseTest {
private static IOrderDao orderDao;
@BeforeClass
public static void init() throws SQLException {
DataSource dataSource = RangeDataSourceFactory.getDataSource();
orderDao = new RangeOrderDaoImpl(dataSource);
}
@Test
public void test() {
orderDao.createTableIfNotExists();
orderDao.truncateTable();
Assert.assertEquals(0, orderDao.select().size());
for (long i = 1; i <= 10; i++) {
Order order = new Order(i, "Order " + i);
orderDao.insert(order);
}
List result = orderDao.select();
result.forEach(System.out::println);
}
}
测试结果:
Order{orderId=350292866448228352, userId=2, details='Order 2'}
Order{orderId=350292866498560000, userId=4, details='Order 4'}
说明:使用 t_order
表中 user_id
字段作为单一分片键,使用 user_id
值对 2 做取模运算,余 0 的存储在 ds_0
中,余 1 的存储在 ds_1
中,因此以上测试中第 1、3、5、7、9 个订单会存储在 ds_1
中,第 2、4、6、8、10 个订单会存储在 ds_0
中。按照定义的范围分片算法逻辑,当 BETWEEN AND
数据范围在 1-5 之间时只会在 ds_0
中查找,数据范围在 6-10 之间时只会在 ds_1
中查找。覆盖后的 DAO 查询条件是 BETWEEN 1 AND 5
,因此只会在 ds_0
中查找,只能找到第 2 个和第 4 个订单。
将 RangeOrderDaoImpl
的 select
方法查询条件修改为 BETWEEN 1 AND 10
,重新执行单元测试可以查询到已插入的所有订单信息,测试结果略。
如果查询范围超过 1-10,如 BETWEEN 1 AND 11
,则执行查询会报以下异常。
java.lang.UnsupportedOperationException
at tutorial.shardingsphere.jdbc.algorithm.RangeModuloShardingDatabaseAlgorithm.doSharding(RangeModuloShardingDatabaseAlgorithm.java:31)
at org.apache.shardingsphere.core.strategy.route.standard.StandardShardingStrategy.doSharding(StandardShardingStrategy.java:71)
at org.apache.shardingsphere.core.strategy.route.standard.StandardShardingStrategy.doSharding(StandardShardingStrategy.java:60)
at org.apache.shardingsphere.core.route.type.standard.StandardRoutingEngine.routeDataSources(StandardRoutingEngine.java:191)
at org.apache.shardingsphere.core.route.type.standard.StandardRoutingEngine.route(StandardRoutingEngine.java:178)
at org.apache.shardingsphere.core.route.type.standard.StandardRoutingEngine.routeByShardingConditionsWithCondition(StandardRoutingEngine.java:108)
at org.apache.shardingsphere.core.route.type.standard.StandardRoutingEngine.routeByShardingConditions(StandardRoutingEngine.java:102)
at org.apache.shardingsphere.core.route.type.standard.StandardRoutingEngine.getDataNodes(StandardRoutingEngine.java:87)
at org.apache.shardingsphere.core.route.type.standard.StandardRoutingEngine.route(StandardRoutingEngine.java:69)
at org.apache.shardingsphere.core.route.router.sharding.ParsingSQLRouter.route(ParsingSQLRouter.java:106)
at org.apache.shardingsphere.core.route.PreparedStatementRoutingEngine.route(PreparedStatementRoutingEngine.java:66)
at org.apache.shardingsphere.core.PreparedQueryShardingEngine.route(PreparedQueryShardingEngine.java:60)
at org.apache.shardingsphere.core.BaseShardingEngine.shard(BaseShardingEngine.java:64)
at org.apache.shardingsphere.shardingjdbc.jdbc.core.statement.ShardingPreparedStatement.shard(ShardingPreparedStatement.java:224)
at org.apache.shardingsphere.shardingjdbc.jdbc.core.statement.ShardingPreparedStatement.executeQuery(ShardingPreparedStatement.java:109)
at tutorial.shardingsphere.jdbc.dao.impl.OrderDaoImpl.listOrders(OrderDaoImpl.java:82)
at tutorial.shardingsphere.jdbc.dao.impl.RangeOrderDaoImpl.select(RangeOrderDaoImpl.java:17)
at tutorial.shardingsphere.jdbc.RangeShardingDatabaseTest.test(RangeShardingDatabaseTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
总结
- 分片算法支持通过
=
、IN
、BETWEEN
进行数据分片,需要自定义实现。 - 精确分片算法
PreciseShardingAlgorithm
用于处理使用单一键作为分片键的=
和IN
进行分片的场景,需要配合StandardShardingStrategy
使用。 - 范围分片算法
RangeShardingAlgorithm
用于处理使用单一键作为分片键的BETWEEN AND
进行分片的场景,需要配合StandardShardingStrategy
使用。