(本篇博客已于2019-08-28优化更新)
序言:当当开发的一个中间件,sharding-jdbc官网API,听说能用它也能读写分离,但这一篇博客主要讲述的是分库分表的功能。
1、Sharding-JDBC
1.1 历史由来
第一版的分库分表并不是现有的Sharding-JDBC,而是当当的一个内部框架ddframe的数据库模块,
dd-rdb的其中一项功能就是分库,没有分表功能,当时只是做了简单的SQL解析。后来随着ddframe被各个
团队采用,只分库的需求渐渐不够用了,而dd-rdb里面有大量的数据库ORM相关的东西,为了使分库分表这
一核心需求更加纯粹,我们才将其中的分片的部分单独提炼出来并命名为Sharding-JDBC,用于在Java的
JDBC层面提供一层驱动,无缝的处理这方面的需求。
1.2 使用场景
对于关系型数据库数据量很大的情况,需要进行水平拆库和拆表,这种场景很适合使用Sharding-JDBC。
举例说明:假设有一亿数据的用户库,放在 MySQL 数据库里查询性能会比较低,而采用水平拆库,将其分为10
个库,根据用户的ID模10,这样数据就能比较平均的分在10个库中,每个库只有1000w记录,查询性能会大大提
升。分片策略类型非常多,大致分为Hash + Mod、Range、Tag 等。Sharding-JDBC还提供了读写分离的能
力,用于减轻写库的压力。此外,Sharding-JDBC 可以用在 JPA 场景中,如 JPA、Hibernate、Mybatis,
Spring JDBC Template 等任何 Java 的 ORM 框架。Java 的 ORM 框架也都是采用 JDBC 与数据库交互。
这也是我们选择在JDBC层,而非选择一个ORM框架进行开发的原因。我们希望 Sharding-JDBC可以尽量的兼容
所有的Java数据库访问层,并且无缝的接入业务应用
1.3 生态
1.3.1 JDBC 相关的核心功能,包括分库分表、读写分离、分布式主键等。
1.3.2 和数据库相关,但不属于 JDBC 范畴的,将以插件的形式提供,包括柔性事务、数据库的 HA、
数据库 Metadata 管理、配置动态化等。
1.3.3 业务或使用友好度相关的,包括多租户、账户安全、Spring 自定义命名空间、Yaml 配置等
2、Springboot2.0集成Sharding-JDBC
2.1 创建Springboot2.0.3项目

勾选Web,Mysql,Mybatis三个模块。
2.2 添加额外的pom依赖
com.dangdang sharding-jdbc-core 1.5.4 org.projectlombok lombok, 此外,还需要安装lombok插件,然后重新启动,所以提前安装。
2.3 添加application.properties
mybatis.config-locations=classpath:mybatis/mybatis-config.xml #datasource spring.devtools.remote.restart.enabled=false #data source1 spring.datasource.test1.driverClassName=com.mysql.jdbc.Driver spring.datasource.test1.jdbc-url=jdbc:mysql://127.0.0.1:3306/test_msg1 spring.datasource.test1.username=root spring.datasource.test1.password=root #data source2 spring.datasource.test2.driverClassName=com.mysql.jdbc.Driver spring.datasource.test2.jdbc-url=jdbc:mysql://127.0.0.1:3306/test_msg2 spring.datasource.test2.username=root spring.datasource.test2.password=root
2.4 编写配置文件java类
import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSourceFactory;
import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule;
import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy;
import com.springboot2.mjt08.strategy.ModuloDatabaseShardingAlgorithm;
import com.springboot2.mjt08.strategy.ModuloTableShardingAlgorithm;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
//import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;
@Configuration
@MapperScan(basePackages = "com.springboot2.mjt08.mapper", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSourceConfig {
/**
* 配置数据源0,数据源的名称最好要有一定的规则,方便配置分库的计算规则
*
* @return
*/
@Bean(name = "dataSource0")
@ConfigurationProperties(prefix = "spring.datasource.test1")
public DataSource dataSource0() {
return DataSourceBuilder.create().build();
}
/**
* 配置数据源1,数据源的名称最好要有一定的规则,方便配置分库的计算规则
*
* @return
*/
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 配置数据源规则,即将多个数据源交给sharding-jdbc管理,并且可以设置默认的数据源,
* 当表没有配置分库规则时会使用默认的数据源
*
* @param dataSource0
* @param dataSource1
* @return
*/
@Bean
public DataSourceRule dataSourceRule(@Qualifier("dataSource0") DataSource dataSource0,
@Qualifier("dataSource1") DataSource dataSource1) {
Map<String, DataSource> dataSourceMap = new HashMap<>(); //设置分库映射
dataSourceMap.put("dataSource0", dataSource0);
dataSourceMap.put("dataSource1", dataSource1);
return new DataSourceRule(dataSourceMap, "dataSource0");//设置默认库,两个库以上时必须设置默认库。默认库的数据源名称必须是dataSourceMap的key之一
}
/**
* 配置数据源策略和表策略,具体策略需要自己实现
*
* @param dataSourceRule
* @return
*/
@Bean
public ShardingRule shardingRule(DataSourceRule dataSourceRule) {
//具体分库分表策略
TableRule orderTableRule = TableRule.builder("t_order")
.actualTables(Arrays.asList("t_order_0", "t_order_1"))
.tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
.dataSourceRule(dataSourceRule)
.build();
//绑定表策略,在查询时会使用主表策略计算路由的数据源,因此需要约定绑定表策略的表的规则需要一致,可以一定程度提高效率
List<BindingTableRule> bindingTableRules = new ArrayList<BindingTableRule>();
bindingTableRules.add(new BindingTableRule(Arrays.asList(orderTableRule)));
return ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Arrays.asList(orderTableRule))
.bindingTableRules(bindingTableRules)
.databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
.tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
.build();
}
/**
* 创建sharding-jdbc的数据源DataSource,MybatisAutoConfiguration会使用此数据源
*
* @param shardingRule
* @return
* @throws SQLException
*/
@Bean(name = "dataSource")
public DataSource shardingDataSource(ShardingRule shardingRule) throws SQLException {
return ShardingDataSourceFactory.createDataSource(shardingRule);
}
/**
* 需要手动配置事务管理器
*
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager transactitonManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
return bean.getObject();
}
@Bean(name = "test1SqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
import com.google.common.collect.Range;
import java.util.Collection;
import java.util.LinkedHashSet;
public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {
@Override
public String doEqualSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
for (String each : databaseNames) {
if (each.endsWith(Long.parseLong(shardingValue.getValue().toString()) % 2 + "")) {
return each;
}
}
throw new IllegalArgumentException();
}
@Override
public Collection<String> doInSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(databaseNames.size());
for (Long value : shardingValue.getValues()) {
for (String tableName : databaseNames) {
if (tableName.endsWith(value % 2 + "")) {
result.add(tableName);
}
}
}
return result;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(databaseNames.size());
Range<Long> range = (Range<Long>) shardingValue.getValueRange();
for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : databaseNames) {
if (each.endsWith(i % 2 + "")) {
result.add(each);
}
}
}
return result;
}
}
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
import com.google.common.collect.Range;
import java.util.Collection;
import java.util.LinkedHashSet;
public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {
@Override
public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
for (String each : tableNames) {
if (each.endsWith(shardingValue.getValue() % 2 + "")) {
return each;
}
}
throw new IllegalArgumentException();
}
@Override
public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(tableNames.size());
for (Long value : shardingValue.getValues()) {
for (String tableName : tableNames) {
if (tableName.endsWith(value % 2 + "")) {
result.add(tableName);
}
}
}
return result;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(tableNames.size());
Range<Long> range = (Range<Long>) shardingValue.getValueRange();
for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : tableNames) {
if (each.endsWith(i % 2 + "")) {
result.add(each);
}
}
}
return result;
}
}
2.5 编写controller、entity、service、mapper、*.xml
package com.springboot2.mjt08.entity;
import com.springboot2.mjt08.enums.UserSexEnum;
import java.io.Serializable;
public class UserEntity implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long order_id;
private Long user_id;
private String userName;
private String passWord;
private UserSexEnum userSex;
private String nickName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getOrder_id() {
return order_id;
}
public void setOrder_id(Long order_id) {
this.order_id = order_id;
}
public Long getUser_id() {
return user_id;
}
public void setUser_id(Long user_id) {
this.user_id = user_id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public UserSexEnum getUserSex() {
return userSex;
}
public void setUserSex(UserSexEnum userSex) {
this.userSex = userSex;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
@Mapper
public interface User1Mapper {
List<UserEntity> getAll();
void update(UserEntity user);
void insert(UserEntity userEntity);
}
public enum UserSexEnum {
MAN, WOMAN
}
@Slf4j
@Service
public class User1Service {
@Autowired
private User1Mapper user1Mapper;
public List<UserEntity> getUsers() {
List<UserEntity> users = user1Mapper.getAll();
return users;
}
//@Transactional(value="test1TransactionManager",rollbackFor = Exception.class,timeout=36000) //说明针对Exception异常也进行回滚,如果不标注,则Spring 默认只有抛出 RuntimeException才会回滚事务
public void updateTransactional(UserEntity user) {
try {
user1Mapper.insert(user);
log.error(String.valueOf(user));
} catch (Exception e) {
log.error("find exception!");
throw e; // 事物方法中,如果使用trycatch捕获异常后,需要将异常抛出,否则事物不回滚。
}
}
}
@RestController
public class UserController {
@Autowired
private User1Service user1Service;
@RequestMapping("/getUsers")
public List<UserEntity> getUsers() {
List<UserEntity> users = user1Service.getUsers();
return users;
}
//测试
@RequestMapping(value = "/update1")
public String updateTransactional() {
UserEntity user2 = new UserEntity();
user2.setId(16L);
user2.setUser_id(53L);
user2.setOrder_id(5L);
user2.setNickName("chengjian");
user2.setPassWord("123445");
user2.setUserName("jj");
user2.setUserSex(UserSexEnum.WOMAN);
user1Service.updateTransactional(user2);
return "test";
}
}
2.6 解析分库分表代码


上面这张图意思是user_id或者order_id 除以2,整除的话去那个库或者去那个表
2.7 创建数据库和表
test_msg1
===t_order_0
===t_order_1
===user
test_msg2
===t_order_0
===t_order_1
===user
创建两个库,我的是test_msg1 test_msg2,然后分别建立两张数据结构相同的表,但是表名不同 ,然后在创建一张user表
CREATE TABLE `t_order_0` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `order_id` varchar(32) DEFAULT NULL COMMENT '顺序编号', `user_id` varchar(32) DEFAULT NULL COMMENT '用户编号', `userName` varchar(32) DEFAULT NULL COMMENT '用户名', `passWord` varchar(32) DEFAULT NULL COMMENT '密码', `user_sex` varchar(32) DEFAULT NULL, `nick_name` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8,分别创建四张表, CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `name` varchar(255) DEFAULT NULL COMMENT '名字', `age` int(11) NOT NULL COMMENT '年龄', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'
2.7 运行测试
最终,通过user_id 决定分库,order_id 决定那张表
3、配置项说明
3.1 ShardingDataSourceFactory
数据分片的数据源创建工厂。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| dataSourceMap | Map<String, DataSource> | 数据源配置 |
| shardingRuleConfig | ShardingRuleConfiguration | 数据分片配置规则 |
| props (?) | Properties | 属性配置 |
| configMap (?) | Map<String, Object> | 用户自定义配置 |
3.2 ShardingRuleConfiguration

3.3 TableRuleConfiguration
表分片规则配置对象。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| logicTable | String | 逻辑表名称 |
| actualDataNodes (?) | String | 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点。用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况 |
| databaseShardingStrategyConfig (?) | ShardingStrategyConfiguration | 分库策略,缺省表示使用默认分库策略 |
| tableShardingStrategyConfig (?) | ShardingStrategyConfiguration | 分表策略,缺省表示使用默认分表策略 |
| logicIndex (?) | String | 逻辑索引名称,对于分表的Oracle/PostgreSQL数据库中DROP INDEX XXX语句,需要通过配置逻辑索引名称定位所执行SQL的真实分表 |
| keyGeneratorColumnName (?) | String | 自增列名称,缺省表示不使用自增主键生成器 |
| keyGenerator (?) | KeyGenerator | 自增列值生成器,缺省表示使用默认自增主键生成器 |
3.4 StandardShardingStrategyConfiguration
ShardingStrategyConfiguration的实现类,用于单分片键的标准分片场景。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| shardingColumn | String | 分片列名称 |
| preciseShardingAlgorithm | PreciseShardingAlgorithm | 精确分片算法,用于=和IN |
| rangeShardingAlgorithm (?) | RangeShardingAlgorithm | 范围分片算法,用于BETWEEN |
3.5 ComplexShardingStrategyConfiguration
ShardingStrategyConfiguration的实现类,用于多分片键的复合分片场景。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| shardingColumns | String | 分片列名称,多个列以逗号分隔 |
| shardingAlgorithm | ComplexKeysShardingAlgorithm | 复合分片算法 |
3.6 InlineShardingStrategyConfiguration
ShardingStrategyConfiguration的实现类,用于配置行表达式分片策略。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| shardingColumn | String | 分片列名称 |
| algorithmExpression | String | 分片算法行表达式,需符合groovy语法,详情请参考行表达式 |
3.7 HintShardingStrategyConfiguration
ShardingStrategyConfiguration的实现类,用于配置Hint方式分片策略。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| shardingAlgorithm | HintShardingAlgorithm | Hint分片算法 |
3.8 ShardingPropertiesConstant
属性配置项,可以为以下属性。
| 名称 | 数据类型 | 说明 |
|---|---|---|
| sql.show (?) | boolean | 是否开启SQL显示,默认值: false |
| executor.size (?) | int | 工作线程数量,默认值: CPU核数 |
上面配置说明均来自官网Sharding-Sphere
来源:oschina
链接:https://my.oschina.net/u/3209213/blog/1835657