1. 前言
ShardingSphere-JDBC 是 Apache ShardingSphere 的第一個產品,也是 Apache ShardingSphere 的前身。 定位為輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務。 它使用客戶端直連數據庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全兼容 JDBC 和各種 ORM 框架。
- 適用於任何基於 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
- 支持任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
- 支持任意實現 JDBC 規范的數據庫,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 標准的數據庫。

下面基於ShardingSphere-JDBC實現分庫分表。框架基於SpringBoot2.1.2.RELEASE,結合 shardingsphere和 mybatis-plus實現。
2. 分庫分表實現
2.1 數據庫搭建
user_db_1(ds0)
├── user_0
└── user_1
user_db_2(ds1)
├── user_0
└── user_1
數據庫user_db_1(別名:ds0)
CREATE DATABASE /*!32312 IF NOT EXISTS*/`user_db_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; USE `user_db_1`; /*Table structure for table `user_0` */ DROP TABLE IF EXISTS `user_0`; CREATE TABLE `user_0` ( `id` bigint(20) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*Table structure for table `user_1` */ DROP TABLE IF EXISTS `user_1`; CREATE TABLE `user_1` ( `id` bigint(20) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
數據庫user_db_2(別名:ds1)
CREATE DATABASE /*!32312 IF NOT EXISTS*/`user_db_2` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; USE `user_db_2`; /*Table structure for table `user_0` */ DROP TABLE IF EXISTS `user_0`; CREATE TABLE `user_0` ( `id` bigint(20) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*Table structure for table `user_1` */ DROP TABLE IF EXISTS `user_1`; CREATE TABLE `user_1` ( `id` bigint(20) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
數據庫結構和表結構需要保持一致:

2.2 搭建工程
2.2.1 依賴
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.zang</groupId> <artifactId>shadingjdbc</artifactId> <version>0.0.1-SNAPSHOT</version> <name>shadingjdbc</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.0.0-RC1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
2.2.2 實體類
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; @Data @TableName("user") public class User extends Model<User> { private Long id; private String name; private Integer age; }
2.2.3 dao層
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.zang.shadingjdbc.model.User; import org.springframework.stereotype.Repository; @Repository public interface UserMapper extends BaseMapper<User> { }
2.2.4 service層
import com.baomidou.mybatisplus.extension.service.IService; import com.zang.shadingjdbc.model.User; import java.util.List; public interface UserService extends IService<User> { void insert(User user); User findById(Long id); List<User> findAll(); }
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.zang.shadingjdbc.mapper.UserMapper; import com.zang.shadingjdbc.model.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{ @Autowired private UserMapper userMapper; @Override public void insert(User entity) { userMapper.insert(entity); } @Override public User findById(Long id) { return userMapper.selectById(id); } @Override public List<User> findAll() { return userMapper.selectList(Wrappers.<User>lambdaQuery()); } }
2.3 分庫分表配置
ShardingSphere-JDBC在工程中的核心就是其配置。這里使用配置文件方式實現分庫以及分表。
# 數據源 ds0,ds1 spring.shardingsphere.datasource.names=ds0,ds1 # 第一個數據庫 spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/user_db_1?characterEncoding=utf-8&&serverTimezone=GMT%2B8 spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=123 # 第二個數據庫 spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/user_db_2?characterEncoding=utf-8&&serverTimezone=GMT%2B8 spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=123 # 指定course表里面主鍵cid 生成策略 SNOWFLAKE spring.shardingsphere.sharding.tables.user.key-generator.column=id spring.shardingsphere.sharding.tables.user.key-generator.type=SNOWFLAKE # 水平拆分的數據庫(表) 配置分庫 + 分表策略 行表達式分片策略 # 分庫策略 id為偶數添加到ds0,奇數添加到ds1 spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=id spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{id % 2} # 分表策略 其中user為邏輯表 分表主要取決於age行 spring.shardingsphere.sharding.tables.user.actual-data-nodes=ds$->{0..1}.user_$->{0..1} spring.shardingsphere.sharding.tables.user.table-strategy.inline.sharding-column=age # 分片算法表達式 age為偶數時分配到user_0,奇數時分配到user_1 spring.shardingsphere.sharding.tables.user.table-strategy.inline.algorithm-expression=user_$->{age % 2} # 打開SQL輸出日志 spring.shardingsphere.props.sql.show=true # 一個實體類對應兩張表,覆蓋 spring.main.allow-bean-definition-overriding=true
部分說明:
邏輯表 user
水平拆分的數據庫(表)的相同邏輯和數據結構表的總稱。例:用戶數據根據主鍵尾數拆分為2張表,分別是user0到user1,他們的邏輯表名為user。
真實表
在分片的數據庫中真實存在的物理表。即上個示例中的user0到user1
分片算法:
Hint分片算法
對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。
分片策略:
行表達式分片策略 對應InlineShardingStrategy。使用Groovy的表達式,提供對SQL語句中的=和IN的分片操作支持,只支持單分片鍵。對於簡單的分片算法,可以通過簡單的配置使用,從而避免繁瑣的Java代碼開發,如: user$->{id % 2} 表示user表根據id模2,而分成2張表,表名稱為user0到user_1。
自增主鍵生成策略
通過在客戶端生成自增主鍵替換以數據庫原生自增主鍵的方式,做到分布式主鍵無重復。 采用UUID.randomUUID()的方式產生分布式主鍵。或者 SNOWFLAKE
2.4 單元測試
這里使用單元測試來模擬業務調用。
import com.zang.shadingjdbc.model.User; import com.zang.shadingjdbc.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class ShadingjdbcApplicationTests { @Autowired private UserService userService; @Test public void addUserDb() { for (int i = 21; i < 40; i++) { User user = new User(); user.setName("zhangsan"+i); user.setAge(i); userService.insert(user); } } @Test public void findAllUser() { List<User> userList = userService.findAll(); System.out.println(userList.size()); } @Test public void findUser() { User user = userService.findById(1346047719665295361L); System.out.println(user.getName()); } }
2.4.1 數據存儲
執行單元測試的 addUserDb方法,查看數據是按照配置的策略進行存儲。

2.4.2 單個數據查詢
執行單元測試的 findUser方法,可以看到其根據分庫策略匹配到ds1庫,對庫中的表進行查詢;因為分表字段age沒有傳入,所以沒有定位到ds1中的表,查詢執行了兩次。

2.4.3 查詢所有數據
執行單元測試的 findAllUser方法,可以看到其將所有庫表都查詢一遍。

以上,即實現了簡單的分庫分表。
