简介
1、ShardingJDBC简介
官方中文文档:概览 :: ShardingSphere
定位为轻量级 Java框架,在 Java 的 JDBC层提供的额外服务,它使用客户端直连数据库,以 jar包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC驱动,完全兼容 JDBC 和各种 ORM框架。
-
适用于任何基于 JDBC 的 ORM框架,如:JPA、Hibernate、Mybatis、Spring JDBC Template 或是直接使用 JDBC;
-
支持任何第三方的数据库连接池,如:DBCP、C3P0、BoneCP、HikariCP 等;
-
支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。
1.1、逻辑表、真实表
-
逻辑表:相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。 例如:订单数据根据主键尾数拆分为 10张表,分别是 t_order_0 到 t_order_9,他们的逻辑表名都为 t_order
-
真实表:在水平拆分的数据库中真实存在的物理表。就是 t_order_0 到 t_order_9。
2、简单使用
2.1、实践1 - 分库写入
将数据按照规则分库写入进去。
2.1.1、引入依赖
<dependencies> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies>
2.1.2、准备数据库环境
需要两个相同库表结构的数据库,最好是两个主机的 MySQL数据库。我这里是同一个主机的两个数据库。
如果是云服务的话,需要开放外网连接:
update user set host = '%' where user = 'root'; FLUSH PRIVILEGES;
2.1.3、pojo、mapper、service、serviceImpl
@Data @NoArgsConstructor @AllArgsConstructor @TableName("user") public class User implements Serializable { private static final long serialVersionUID = 1L; private int id; private String name; private String password; }
public interface UserMapper extends BaseMapper<User> {}
public interface UserService extends IService<User> {} @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
2.1.4、修改配置文件
推荐查看官方文档。
server: port: 8888 spring: application: name: sharding-jdbc shardingsphere: datasource: # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式 names: db0,db1 # 为每个数据源单独进行配置 db0: # 数据源实现类,这里使用默认的 HikariDataSource type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1 username: root password: 123456 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2 username: root password: 123456 rules: sharding: tables: #这里填写表名称,程序中对这张表的所有操作,都会先采用下面的路由方案 user: #这里填写实际的路由节点,比如现在我们要分两个库,那么就可以把两个库都写上,以及对应的表 #也可以使用表达式,比如下面的可以简写为 db$->{0..1}.user actual-data-nodes: db0.user,db1.user #这里是分库策略配置 database-strategy: #这里选择标准策略,也可以配置复杂策略 complex,基于多个键进行分片 standard: #参与分片运算的字段,下面的算法会根据这里提供的字段进行运算 # 就是取模算法,为 0 就放 db0,为 1 就放 db1 sharding-column: id #这里填写我们下面自定义的算法名称 sharding-algorithm-name: my-alg sharding-algorithms: #自定义一个新的算法,名称随意 my-alg: #算法类型,官方内置了很多种,这里演示最简单的一种 type: MOD props: sharding-count: 2 props: #开启日志,一会方便我们观察 sql-show: true
2.1.5、分库插入测试
@SpringBootTest class ShardingJdbcTestApplicationTest { @Resource UserService userService; @Test void batchInsertTest() { for (int i = 0; i < 9; i++) { userService.save(new User(i, "user-" + i, "password-" + i)); } } }
2.1.5.1、执行语句日志
ShardingJDBC 会把 MyBatis-Plus 要去执行的 SQL语句当作逻辑SQL 进行分析,然后修改后再执行。依次分为 Logic SQL、SQLStatement、Actual SQL。
2022-07-21 17:50:02.873 INFO 8552 --- [ main] com.chw.ShardingJdbcTestApplicationTest : Started ShardingJdbcTestApplicationTest in 4.937 seconds (JVM running for 6.572) ShardingSphere-SQL:Logic SQL:INSERT INTO user (id,name,password) VALUES (?,?,?) ShardingSphere-SQL:SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) ShardingSphere-SQL:Actual SQL: db0 ::: INSERT INTO user (id,name,password ) VALUES (?, ?, ?) ::: [0, user-0, password-0] ShardingSphere-SQL : Logic SQL: INSERT INTO user ( id, name, password ) VALUES ( ?, ?, ? ) 2022-07-21 17:50:04.133 INFO 8552 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-07-21 17:50:04.133 INFO 8552 --- [ main] ShardingSphere-SQL : Actual SQL: db1 ::: INSERT INTO user ( id, name, password ) VALUES (?, ?, ?) ::: [1, user-1, password-1]
2.1.5.2、数据库结果
2.2、实践2 - 分表写入
将数据按照规则写入同一个数据库的不同表中。
2.2.1、准备数据库环境
创建 user_0、user_1。
2.2.2、修改配置文件
虽然我们想把数据分表写入 user_0、user_1,但是我们不需要修改业务代码,因为这两个表的逻辑表名称都是 user,所以只需要在逻辑上给 user表插入数据即可,ShardingJDBC 会在物理上根据规则进行分表。
server: port: 8888 spring: application: name: sharding-jdbc shardingsphere: datasource: # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式 names: db0,db1 # 为每个数据源单独进行配置 db0: # 数据源实现类,这里使用默认的 HikariDataSource type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1 username: root password: 123456 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2 username: root password: 123456 rules: sharding: tables: user: # 也可以写成 db0.user0,db0.user1 actual-data-nodes: db0.user_$->{0..1} #现在我们来配置一下分表策略,之前是 database-strategy table-strategy: #基本都跟之前是一样的 standard: sharding-column: id sharding-algorithm-name: my-alg sharding-algorithms: my-alg: #这里我们演示一下INLINE方式,我们可以自行编写表达式来决定 type: INLINE props: #比如我们还是希望进行模2计算得到数据该去的表 #只需要给一个最终的表名称就行了 user_,后面的数字是表达式取模算出的 #实际上这样写和 MOD模式一模一样 algorithm-expression: user_$->{id % 2} #查询会根据分片策略进行,但范围查询会无视分片策略,所以要改成 true, # 让范围查询支持分片策略 allow-range-query-with-inline-sharding: true props: #开启日志,一会方便我们观察 sql-show: true
2.2.3、分表插入测试
测试的代码还是一样的。
2.2.3.1、执行语句日志
会自动把 user表变成 user0 或 user1
ShardingSphere-SQL: Logic SQL: INSERT INTO user ( id,name,password ) VALUES ( ?,?,? ) ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) ShardingSphere-SQL: Actual SQL: db0 ::: INSERT INTO user_0 ( id,name,password ) VALUES (?, ?, ?) ::: [0, user-0, password-0] 2022-07-21 19:47:19.539 INFO 14084 --- [ main] ShardingSphere-SQL : Logic SQL: INSERT INTO user ( id, name, password ) VALUES ( ?, ?, ? ) 2022-07-21 19:47:19.539 INFO 14084 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-07-21 19:47:19.539 INFO 14084 --- [ main] ShardingSphere-SQL : Actual SQL: db0 ::: INSERT INTO user_1 ( id,name,password ) VALUES (?, ?, ?) ::: [1, user-1, password-1]
2.2.3.2、数据库结果
2.2.4、分表多查询测试
@Test void batchSelectByIdTest() { ArrayList<Integer> idList = new ArrayList<>(); idList.add(0); idList.add(1); List<User> userList = userService.listByIds(idList); userList.forEach(System.out::println); }
2.2.4.1、执行语句日志
ShardingSphere-SQL: Logic SQL: SELECT id,name,password FROM user WHERE id IN ( ?, ?) ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) ShardingSphere-SQL:Actual SQL:db0:::SELECT id,name,password FROM user_0 WHERE id IN ( ? , ? ) UNION ALL SELECT id,name,password FROM user_1 WHERE id IN ( ? , ? ) ::: [0, 1, 0, 1]
2.2.5、分表范围查询测试
查询会根据分片策略来,但是范围查询会无视分片策略,所以 allow-range-query-with-inline-sharding 要改成 true,让范围查询支持分片策略。
@Test void rangeSelectByIdTest() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.ge("id",1); queryWrapper.le("id",7); List<User> userList = userService.list(queryWrapper); userList.forEach(System.out::println); }
2.2.5.1、执行语句日志
ShardingSphere-SQL: Logic SQL: SELECT id,name,password FROM user WHERE (id >= ? AND id <= ?) ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) ShardingSphere-SQL: Actual SQL: db0 ::: SELECT id,name,password FROM user_0 WHERE (id >= ? AND id <= ?) UNION ALL SELECT id,name,password FROM user_1 WHERE (id >= ? AND id <= ?) ::: [1, 7, 1, 7]
2.3、实践3 - 雪花算法
2.3.1、主键数据类型改成 bigint
ALTER TABLE `yyds`.`test` MODIFY COLUMN `id` bigint NOT NULL FIRST;
2.3.2、修改配置文件
server: port: 8888 spring: application: name: sharding-jdbc shardingsphere: datasource: # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式 names: db0,db1 # 为每个数据源单独进行配置 db0: # 数据源实现类,这里使用默认的 HikariDataSource type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1 username: root password: 123456 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2 username: root password: 123456 rules: sharding: tables: user: actual-data-nodes: db0.user,db1.user # 分库策略 database-strategy: standard: sharding-column: id sharding-algorithm-name: my-alg # 使用自定义的主键生成策略 key-generate-strategy: column: id key-generator-name: my-gen key-generators: #这里写我们自定义的主键生成算法 my-gen: # 使用雪花算法 type: SNOWFLAKE props: # 工作机器ID,保证唯一就行 worker-id: 666 sharding-algorithms: my-alg: type: MOD props: sharding-count: 2 props: #开启日志,一会方便我们观察 sql-show: true
2.3.3、修改代码
因为我们使用的是 MyBatis-Plus,它提供的方法都是包含全部字段的,如果我们不传值,它就会设置一个默认值,那么 ShardingJDBC 就会以为你已经设置了主键值了,就不需要用雪花算法帮你生产了。所以我们需要自己指定一个 SQL语句。
@Mapper public interface UserMapper extends BaseMapper<User> { @Insert("insert into user(name, password) values(#{name}, #{password})") int addUser(User user); }
2.3.4、测试
@Resource UserMapper userMapper; @Test void batchInsertWithoutIdTest() { for (int i = 0; i < 10; i++) { User user = new User(); user.setName("user-" + i); user.setPassword("password-" + i); userMapper.addUser(user); } }
2.3.4.1、执行语句日志
ShardingSphere-SQL: Logic SQL: insert into user(name, password) values(?, ?) ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) ShardingSphere-SQL: Actual SQL: db0 ::: insert into user(name, password, id) values(?, ?, ?) ::: [user-10, password-10, 757242293794111488] 2022-07-22 14:10:31.814 INFO 11732 --- [ main] ShardingSphere-SQL : Logic SQL: insert into user(name, password) values(?, ?) 2022-07-22 14:10:31.814 INFO 11732 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-07-22 14:10:31.814 INFO 11732 --- [ main] ShardingSphere-SQL : Actual SQL: db1 ::: insert into user(name, password, id) values(?, ?, ?) ::: [user-11, password-11, 757242294175793153]
2.4、实践4 - 读写分离
2.4.1、数据库环境配置
记得创建好数据表。
2.4.1.1、从库改成只读模式
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf # 加入这行代码 read-only = 1 # 重启下 MySQL sudo systemctl restart mysql.service
2.4.1.2、配置主从关系
# 主库 # 进入 MySQL数据库 sudo mysql -u root -p # 展示状态数据 show master status
# 从库 # 进入 MySQL数据库 sudo mysql -u root -p # 根据主库的状态数据配置主从关系 change replication source to SOURCE_HOST='192.168.0.8',SOURCE_USER='test',SOURCE_PASSWORD='123456',SOURCE_LOG_FILE='binlog.000007',SOURCE_LOG_POS=156; # 开启主从关系 start replica;
2.4.2、修改配置文件
server: port: 8888 spring: application: name: sharding-jdbc shardingsphere: datasource: # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式 names: db0,db1 # 为每个数据源单独进行配置 db0: # 数据源实现类,这里使用默认的 HikariDataSource type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1 username: root password: 123456 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2 username: root password: 123456 rules: #配置读写分离 readwrite-splitting: data-sources: #这个名称随便写 user-db: #使用静态类型,如果使用动态 Dynamic类型可以自动发现auto-aware-data-source-name type: Static props: #配置写库,即主库(只能一个) write-data-source-name: db0 #配置从库(多个,逗号隔开) read-data-source-names: db1 #读数据的负载均衡策略,可以自定义 load-balancer-name: my-load load-balancers: #自定义的负载均衡策略 my-load: type: ROUND_ROBIN props: #开启日志,一会方便我们观察 sql-show: true
2.4.3、测试
可以插入一个数据,然后再查一下看看是哪个库。