1.1、ShardingSphere简介
sharding-jdbc是ShardingSphere的其中一个模块,摘抄官网一段简介:
(官方中文文档:https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sql/)
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与NoSQL和NewSQL是并存而非互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
1.2、sharding-jdbc简介
定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
-
- 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
功能列表:
数据分片
-
- 分库 & 分表
- 读写分离
- 分片策略定制化
- 无中心化分布式主键
分布式事务
-
- 标准化事务接口
- XA强一致事务
- 柔性事务
数据库治理
-
- 配置动态化
- 编排 & 治理
- 数据脱敏
- 可视化链路追踪
- 弹性伸缩(规划中)
1.3、sharding-jdbc核心概念
-
- 逻辑表(LogicTable):进行水平拆分的时候同一类型(逻辑、数据结构相同)的表的总称。例:订单数据根据主键尾数拆分为10张表,分别是
t_order_0
到t_order_9
,他们的逻辑表名为t_order
。 - 真实表(ActualTable):在分片的数据库中真实存在的物理表。即上个示例中的
t_order_0
到t_order_9
。 - 数据节点(DataNode):数据分片的最小单元。由数据源名称和数据表组成,例:
ds_0.t_order_0
。 - 动态表(DynamicTable):逻辑表和物理表不一定需要在配置规则中静态配置。如,按照日期分片的场景,物理表的名称随着时间的推移会产生变化。
-
广播表:指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
- 绑定表(BindingTable):指分片规则一致的主表和子表。例如:
t_order
表和t_order_item
表,均按照order_id
分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:
- 逻辑表(LogicTable):进行水平拆分的时候同一类型(逻辑、数据结构相同)的表的总称。例:订单数据根据主键尾数拆分为10张表,分别是
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
在不配置绑定表关系时,假设分片键order_id
将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
在配置绑定表关系后,路由的SQL应该为2条:
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11); SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
其中t_order
在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么t_order_item
表的分片计算将会使用t_order
的条件。故绑定表之间的分区键要完全相同。
-
- 分片键(ShardingColumn):分片字段用于将数据库(表)水平拆分的字段,支持单字段及多字段分片。例如上例中的order_id。
- 分片算法(ShardingAlgorithm):进行水平拆分时采用的算法,分片算法需要应用方开发者自行实现,可实现的灵活度非常高。目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。
1、精确分片算法 对应PreciseShardingAlgorithm,必选,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。 2、范围分片算法 对应RangeShardingAlgorithm,可选,用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。需要配合StandardShardingStrategy使用。 3、复合分片算法 对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。 4、Hint分片算法 对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。
-
- 分片策略(ShardingStrategy):包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。
1、标准分片策略 对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。 2、复合分片策略 对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。 3、行表达式分片策略 对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。 4、Hint分片策略 对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。 5、不分片策略 对应NoneShardingStrategy。不分片的策略。
二、 Sharding-JDBC 实战
2.1、引入依赖
核心依赖是:sharding-jdbc-core
注意组织名称(groupId)是 org.apache.shardingsphere,捐献给 Apache 之后(课程中统一用这个)。
io.shardingjdbc :更名之前(废弃)
io.shardingsphere:更名之后(废弃)
包名和某些类有差异,如果升级需要注意,import 的包名都需要修改。
Apache 的包为 Spring Boot 提供了 starter 的依赖:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version> </dependency>
2.2、 API 使用
先看 API 是怎么用的,也就是自己像 JDBC 的代码一样创建数据源,创建连接。后面再看在 Spring 里面怎么使用。在写之前我们先来写下原生的JDBC连接操作数据库方法,到时可以跟sharding-jdbc的比较
public class JdbcTest { public static void main(String[] args) { Connection connection=null; Statement statement=null; Student student = new Student(); try { // 注册 JDBC 驱动 Class.forName("com.mysql.jdbc.Driver"); // 打开连接 connection = DriverManager.getConnection("jdbc:mysql://192.168.2.106:3306/ljxmycat", "root", "root"); // 执行查询 statement = connection.createStatement(); String sql = "SELECT sid, name,qq FROM student where sid = 1"; ResultSet rs = statement.executeQuery(sql); // 获取结果集 while (rs.next()) { Integer sid = rs.getInt("sid"); String name = rs.getString("name"); String qq = rs.getString("qq"); student.setSid(sid); student.setName(name); student.setQq(qq); } System.out.println(student.toString()); rs.close(); statement.close(); connection.close(); }catch (SQLException se){ se.printStackTrace(); }catch (Exception e){ e.printStackTrace(); }finally { try { if (statement != null) { statement.close(); } } catch (SQLException se2) { } try { if (connection != null) { connection.close(); } } catch (SQLException se) { se.printStackTrace(); } } } }
2.2.1 、数据分片
public class ShardJDBCTest { public static void main(String[] args) throws SQLException { // 配置真实数据源 Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第一个数据源 DruidDataSource dataSource1 = new DruidDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setUrl("jdbc:mysql://192.168.2.106:3306/ljxmycat0"); dataSource1.setUsername("root"); dataSource1.setPassword("root"); dataSourceMap.put("ljxmycat0", dataSource1); // 配置第二个数据源 DruidDataSource dataSource2 = new DruidDataSource(); dataSource2.setDriverClassName("com.mysql.jdbc.Driver"); dataSource2.setUrl("jdbc:mysql://192.168.2.106:3306/ljxmycat1"); dataSource2.setUsername("root"); dataSource2.setPassword("root"); dataSourceMap.put("ljxmycat1", dataSource2); // 配置Order表规则 TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("student", "ljxmycat${0..1}.student"); // 分库策略,使用inline实现 InlineShardingStrategyConfiguration dataBaseInlineStrategy = new InlineShardingStrategyConfiguration("sid", "ljxmycat${sid % 2}"); orderTableRuleConfig.setDatabaseShardingStrategyConfig(dataBaseInlineStrategy); // 分表策略,使用inline实现(没有分表,为什么不分表?) InlineShardingStrategyConfiguration tableInlineStrategy = new InlineShardingStrategyConfiguration("sid", "student"); orderTableRuleConfig.setTableShardingStrategyConfig(tableInlineStrategy); // 添加表配置 ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration(); shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig); // 获取数据源对象 DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties()); String sql = "SELECT * from student WHERE qq=?"; try { Connection conn = dataSource.getConnection(); PreparedStatement preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, "166669999"); System.out.println(); try (ResultSet rs = preparedStatement.executeQuery()) { while (rs.next()) { // %2结果,路由到 System.out.println("---------name:" + rs.getString(2)); } } } catch (Exception e) { e.printStackTrace(); } } }
ShardingDataSourceFactory 利用 ShardingRuleConfiguration 创建数据源。ShardingRuleConfiguration 可以包含多个 TableRuleConfiguration(多张表),每张表都可以通过 ShardingStrategyConfiguration 设置自己的分库和分表策略。
有了数据源,就可以走 JDBC 的流程了。
2.2.2 、读写分离
public class MasterSlaveTest { public static void main(String[] args) throws SQLException { // 配置真实数据源 Map<String, DataSource> dataSourceMap = new HashMap<>(); // 配置第一个数据源 DruidDataSource dataSource1 = new DruidDataSource(); dataSource1.setDriverClassName("com.mysql.jdbc.Driver"); dataSource1.setUrl("jdbc:mysql://192.168.2.105:3306/ljxmycat"); dataSource1.setUsername("root"); dataSource1.setPassword("root"); dataSourceMap.put("master0", dataSource1); // 配置第二个数据源 DruidDataSource dataSource2 = new DruidDataSource(); dataSource2.setDriverClassName("com.mysql.jdbc.Driver"); dataSource2.setUrl("jdbc:mysql://192.168.2.106:3306/ljxmycat"); dataSource2.setUsername("root"); dataSource2.setPassword("root"); dataSourceMap.put("slave0", dataSource2); // 配置读写分离规则 MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration("qs_master_slave", "master0", Arrays.asList("slave0")); // 获取数据源对象 DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource(dataSourceMap, masterSlaveRuleConfig, new Properties()); Connection conn = dataSource.getConnection(); String selectSql = "SELECT * from student where qq=?"; try { PreparedStatement preparedStatement = conn.prepareStatement(selectSql); // 直接在 slave 128 ds0 插入主节点没有的数据: insert into t_order(order_id, user_id) value(26732,26732) preparedStatement.setString(1, "466669999"); System.out.println(); try (ResultSet rs = preparedStatement.executeQuery()) { while (rs.next()) { System.out.println("---------name:" + rs.getString(2)); } } } catch (Exception e) { e.printStackTrace(); } } }
总结一下:
在 JDBC API 中使用,我们可以直接创建数据源。如果在 Spring 中使用,我们自定义的数据源怎么定义使用呢?因为数据源是容器管理的,所以需要通过注解或者 xml 配置文件注入。
2.3 、Spring 中使用
先来总结一下,第一个,使用的数据源需要用 Sharding-JDBC 的数据源。而不是容器或者 ORM 框架定义的。这样才能保证动态选择数据源的实现。当然,流程是先由 Sharding-JDBC 定义,再交给 Druid 放进池子里,再交给 MyBatis,最后再注入到 Spring。最外层是 Spring,因为代码是从 Spring 开始调用的。第二个,因为 Sharding-JDBC 是工作在客户端的,所以我们要在客户端配置分库分表的策略。跟 Mycat 不一样的是,Sharding-JDBC 没有内置各种分片策略和算法,需要我们通过表达式或者自定义的配置文件实现。总体上,需要配置的就是这两个,数据源和分片策略。配置的方式是多种多样的,在官网也有详细的介绍,大家可以根据项目的实际情况进行选择。
2.3.1、 Java 配置
官网中关于java配置写的很详细,反正我们现在是springboot的时代,这里我也赖写了,它的特点是非常灵活,我们可以实现各种定义的分片策略。但是缺点是,如果把数据源、策略都配置在 Java Config 中,就出现了硬编码,在修改的时候比较麻烦。
2.3.2 、Spring Boot 配置
在springboot中我们是直接使用 Spring Boot 的 application.properties 来配置,这个要基于 starter 模块。把数据源和分库分表策略都配置在 properties 文件中。这种方式配置比较简单,但是不能实现复杂的分片策略,不够灵活。
2.3.3 、yml 配置
使用 Spring Boot 的 yml 配置(shardingjdbc.yml),也要依赖 starter模块。当然我们也可以结合不同的配置方式,比如把分片策略放在 JavaConfig 中,数据源配置在 yml 中或 properties 中。
2.4 、Sharding-JDBC 分片案例验证
在同一个服务器上建两个库
数据库ds0,ds1 CREATE TABLE `user_info` ( `user_id` bigint(128) NOT NULL, `user_name` varchar(45) DEFAULT NULL, `account` varchar(45) NOT NULL, `password` varchar(45) DEFAULT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_order` ( `order_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_order_item` ( `item_id` int(11) NOT NULL, `order_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`item_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_config` ( `config_id` int(16) NOT NULL AUTO_INCREMENT, `para_name` varchar(255) DEFAULT NULL, `para_value` varchar(255) DEFAULT NULL, `para_desc` varchar(255) DEFAULT NULL, PRIMARY KEY (`config_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; truncate table user_info; truncate table t_order; truncate table t_order_item; truncate table t_config;
使用以下配置打印路由信息:
spring.shardingsphere.props.sql.show=true
如果直接配置mysql数据源用下面配置就可以:
spring.datasource.url=jdbc:mysql://localhost:3306/ds0 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
但是我们现在用的是sharding--jdbc数据源,用法就有变化:
我们可以定义默认的分库分表策略(配置中注释了),例如:用 user_id 作为分片键。这里用到了一种分片策略的实现,叫做行内表达式。我们对 user_id 取模,然后选择数据库。如果模 2 等于 0,在第一个数据库中。模 2 等于 1,在第二个数据库中。
数据源名称是行内表达式组装出来的。
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds$->{0..1}.user_info
对于不同的表,也可以单独配置分库策略( databaseStrategy )和分表策略(tableStrategy)。案例中只有分库没有分表,所以没定义 tableStrategy
2.4.1 、取模分片
首先我们来验证一下 user_info 表的取模分片(modulo [‘m?djul?u])。我们根据 user_id,把用户数据划分到两个数据节点上。在本地创建两个数据库 ds0 和 ds1,表user_info在上面已经有说明过;
这里只定义了分库策略,没有定义单库内的分表策略,两个库都是相同的表名。 路由的结果:ds0.user_info,ds1.user_info。如果定义了分库策略,两个库里面都有两张表,那么路由的结果可能是 4 种:ds0.user_info0,ds0.user_info1;ds1. user_info0, ds1. user_info1
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds$->{0..1}.user_info
spring.shardingsphere.sharding.tables.user_info.databaseStrategy.inline.shardingColumn=user_id
spring.shardingsphere.sharding.tables.user_info.databaseStrategy.inline.algorithm-expression=ds${user_id % 2}
首先两个数据库的 user_info 表里面没有任何数据。配置完后的applicateion.properties配置文件 如下
# MyBatis配置 mybatis.mapper-locations=classpath:mapper/*.xml mybatis.config-location=classpath:mybatis-config.xml spring.shardingsphere.props.sql.show=true # 数据源配置 spring.shardingsphere.datasource.names=ds0,ds1 spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/ds0 spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=root spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/ds1 spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=root # 默认策略 spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds$->{0..1}.user_info spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds${user_id % 2}
pom.xml导入相关包
<dependencies>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>4.6.2</version>
</dependency>
</dependencies>
创建实体类
ublic class UserInfo { private Long userId; private String userName; private String account; private String password; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName == null ? null : userName.trim(); } public String getAccount() { return account; } public void setAccount(String account) { this.account = account == null ? null : account.trim(); } public String getPassword() { return password; } public void setPassword(String password) { this.password = password == null ? null : password.trim(); } @Override public String toString() { return " -------- UserInfo { " + "userId=" + userId + ", userName=‘" + userName + ‘\‘‘ + ", account=‘" + account + ‘\‘‘ + ", password=‘" + password + ‘\‘‘ + ‘}‘; } }
创建service
@Service public class UserService { @Resource UserInfoMapper userInfoMapper; public static Long userId = 1L; public void insert() { for (int i = 1; i <= 100; i++) { UserInfo userInfo = new UserInfo(); // userInfo.setUserId(userId); userInfo.setAccount("account" + i); userInfo.setPassword("password" + i); userInfo.setUserName("name" + i); userId++; userInfoMapper.insert(userInfo); } } public UserInfo getUserInfoByUserId(Long id){ return userInfoMapper.selectByPrimaryKey(id); } public List<UserInfo> selectByRange(Long firstId, Long lastId){ return userInfoMapper.selectByRange(firstId, lastId); } }
创建mapper
@Mapper public interface UserInfoMapper { int insert(UserInfo record); UserInfo selectByPrimaryKey(Long userId); List<UserInfo> selectByRange(@Param("firstId") Long firstId, @Param("lastId") Long lastId); }
在mapper中添加插入语句
<insert id="insert" parameterType="com.ghy.springbootsharding.entity.UserInfo"> insert into user_info (user_name, account, password) values ( #{userName,jdbcType=VARCHAR}, #{account,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}) </insert>
验证代码
/** * 演示取模的分库分表策略 */ @RunWith(SpringRunner.class) @SpringBootTest @MapperScan(basePackages = "com.ghy.springbootsharding.mapper") public class UserShardingTest { @Resource UserService userService; @Test public void insert(){ userService.insert(); } }
2.4.2 、绑定表
第二种是绑定表,也就是父表和子表有关联关系。主表和子表使用相同的分片策略。表在上面已经上传过就是表t_order和t_order_item
绑定表将使用主表的分片策略。
# 分库算法 t_order 多库分表(er表) spring.shardingsphere.sharding.tables.t_order.databaseStrategy.inline.shardingColumn=order_id spring.shardingsphere.sharding.tables.t_order.databaseStrategy.inline.algorithm-expression=ds${order_id % 2} spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order # 分库算法 t_order_item 多库分表(er表) spring.shardingsphere.sharding.tables.t_order_item.databaseStrategy.inline.shardingColumn=order_id spring.shardingsphere.sharding.tables.t_order_item.databaseStrategy.inline.algorithm-expression=ds${order_id % 2} spring.shardingsphere.sharding.tables.t_order_item.actual-data-nodes=ds$->{0..1}.t_order_item # 绑定表规则列表,防止关联查询出现笛卡尔积 spring.shardingsphere.sharding.binding-tables[0]=t_order,t_order_item
除了定义分库和分表算法之外,我们还需要多定义一个 binding-tables。绑定表不使用分片键查询时,会出现笛卡尔积。至于笛卡尔积的概念相信看这篇幅文章的朋友都会很清楚,下图一张是不使用分片键的效果,另一张是使用后的效果;看过我前面mycat的朋友应该很清楚了mycat 不支持这种二维的路由,要么是分库,要么是分表
配置文件搞定了,下面来写个代码测试下
测试类
@RunWith(SpringRunner.class) @SpringBootTest public class OrderShardingTest { @Resource OrderService orderService; @Test public void insert(){ orderService.insert(); } @Test public void select(){ Order order1 = orderService.getOrderInfoByOrderId(1); System.out.println("------order1:"+order1); Order order2 = orderService.getOrderInfoByOrderId(2); System.out.println("------order2:"+order2); } }
public class Order implements Serializable { private Integer orderId; private Integer userId; public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } @Override public String toString() { return " -------- Order{" + "orderId=" + orderId + ", userId=" + userId + ‘}‘; } }
@Service public class OrderService { @Resource OrderMapper orderMapper; public static Long orderId = 1L; public static Long userId = 1L; public void insert() { for (int i = 1; i <= 100; i++) { Order order = new Order(); order.setOrderId(i); order.setUserId(i); orderId++; userId++; orderMapper.insert(order); } } public Order getOrderInfoByOrderId(Integer id){ return orderMapper.selectByPrimaryKey(id); } }
public interface OrderMapper { int insert(Order record); Order selectByPrimaryKey(Integer orderId); }
<resultMap id="BaseResultMap" type="com.ghy.springbootsharding.entity.Order"> <id column="order_id" jdbcType="INTEGER" property="orderId" /> <result column="user_id" jdbcType="INTEGER" property="userId" /> </resultMap> <sql id="Base_Column_List"> order_id, user_id </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_order where order_id = #{orderId,jdbcType=INTEGER} </select> <insert id="insert" parameterType="com.ghy.springbootsharding.entity.Order"> insert into t_order (order_id, user_id) values (#{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER}) </insert>
测试类
@RunWith(SpringRunner.class) @SpringBootTestpublic class OrderItemShardingTest { @Resource OrderItemService orderItemService; @Test public void insert(){ orderItemService.insert(); } @Test public void select(){ OrderItem orderItem1 = orderItemService.getOrderItemByItemId(1); System.out.println("------orderItem1:"+orderItem1); OrderItem orderItem2 = orderItemService.getOrderItemByItemId(2); System.out.println("------orderItem2:"+orderItem2); } }
public class OrderItem { private Integer itemId; private Integer orderId; private Integer userId; public Integer getItemId() { return itemId; } public void setItemId(Integer itemId) { this.itemId = itemId; } public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } @Override public String toString() { return " -------- OrderItem{" + "itemId=" + itemId + ", orderId=" + orderId + ", userId=" + userId + ‘}‘; } }
@Service public class OrderItemService { @Resource OrderItemMapper orderItemMapper; public void insert() { for (int i = 1; i <= 100; i++) { OrderItem orderItem = new OrderItem(); orderItem.setItemId(i); orderItem.setOrderId(i); orderItem.setUserId(i); orderItemMapper.insert(orderItem); } } public OrderItem getOrderItemByItemId(Integer id){ return orderItemMapper.selectByPrimaryKey(id); } }
public interface OrderItemMapper { int insert(OrderItem record); OrderItem selectByPrimaryKey(Integer itemId); }
<resultMap id="BaseResultMap" type="com.ghy.springbootsharding.entity.OrderItem"> <id column="item_id" jdbcType="INTEGER" property="itemId" /> <result column="order_id" jdbcType="INTEGER" property="orderId" /> <result column="user_id" jdbcType="INTEGER" property="userId" /> </resultMap> <sql id="Base_Column_List"> item_id, order_id, user_id </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_order_item where item_id = #{itemId,jdbcType=INTEGER} </select> <insert id="insert" parameterType="com.ghy.springbootsharding.entity.OrderItem"> insert into t_order_item (item_id, order_id, user_id ) values (#{itemId,jdbcType=INTEGER}, #{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER} ) </insert>
先插入主表的数据,再插入子表的数据。
然后可以用我上图片的关连表查询查的玩玩
2.4.3、 广播表
最后一种是广播表,也就是需要在所有节点上同步操作的数据表。
spring.shardingsphere.sharding.broadcast-tables=t_config
一样配置完成后测试
@RunWith(SpringRunner.class) @SpringBootTest @MapperScan(basePackages = "com.ghy.springbootsharding.mapper") public class ConfigShardingTest { @Resource ConfigService configService; @Test public void insert(){ configService.insert(); } @Test public void update(){ configService.update(1); } @Test public void select(){ Config config1 = configService.geConfigById(1); System.out.println("------config1:"+config1); Config config2 = configService.geConfigById(2); System.out.println("------config2:"+config2); } }
public class Config { private Integer configId; private String paraName; private String paraValue; private String paraDesc; public Integer getConfigId() { return configId; } public void setConfigId(Integer configId) { this.configId = configId; } public String getParaName() { return paraName; } public void setParaName(String paraName) { this.paraName = paraName == null ? null : paraName.trim(); } public String getParaValue() { return paraValue; } public void setParaValue(String paraValue) { this.paraValue = paraValue == null ? null : paraValue.trim(); } public String getParaDesc() { return paraDesc; } public void setParaDesc(String paraDesc) { this.paraDesc = paraDesc == null ? null : paraDesc.trim(); } @Override public String toString() { return " -------- Config {" + "configId=" + configId + ", paraName=‘" + paraName + ‘\‘‘ + ", paraValue=‘" + paraValue + ‘\‘‘ + ", paraDesc=‘" + paraDesc + ‘\‘‘ + ‘}‘; }
@Service public class ConfigService { @Resource ConfigMapper configMapper; public static Long configId = 1L; public void insert() { for (int i = 1; i <= 10; i++) { Config config = new Config(); config.setConfigId(i); config.setParaName("name"+i); config.setParaValue("value"+i); config.setParaDesc("desc"+i); configId++; configMapper.insert(config); } } public void update(Integer configId) { Config config = configMapper.selectByPrimaryKey(configId); config.setParaDesc("after modified. 2673-666"); configMapper.updateByPrimaryKey(config); } public Config geConfigById(Integer id){ return configMapper.selectByPrimaryKey(id); } }
public interface ConfigMapper { int insert(Config config); Config selectByPrimaryKey(Integer configId); int updateByPrimaryKey(Config record); }
<resultMap id="BaseResultMap" type="com.ghy.springbootsharding.entity.Config"> <id column="config_id" jdbcType="INTEGER" property="configId" /> <result column="para_name" jdbcType="VARCHAR" property="paraName" /> <result column="para_value" jdbcType="VARCHAR" property="paraValue" /> <result column="para_desc" jdbcType="VARCHAR" property="paraDesc" /> </resultMap> <sql id="Base_Column_List"> config_id, para_name, para_value, para_desc </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from t_config where config_id = #{configId,jdbcType=INTEGER} </select> <insert id="insert" parameterType="com.ghy.springbootsharding.entity.Config"> insert into t_config (config_id, para_name, para_value, para_desc) values (#{configId,jdbcType=INTEGER}, #{paraName,jdbcType=VARCHAR}, #{paraValue,jdbcType=VARCHAR}, #{paraDesc,jdbcType=VARCHAR}) </insert> <update id="updateByPrimaryKey" parameterType="com.ghy.springbootsharding.entity.Config"> update t_config set para_name = #{paraName,jdbcType=VARCHAR}, para_value = #{paraValue,jdbcType=VARCHAR}, para_desc = #{paraDesc,jdbcType=VARCHAR} where config_id = #{configId,jdbcType=INTEGER} </update>
2.4.4 、读写分离
读写分离的API实现前面已经写过了,在springboot里面,读写分离变的更加简单只用简单的几个配置
master-slave-rules: #这里配置读写分离的时候一定要记得添加主库的数据源名称 这里为master0
master0: #指定master0为主库,slave0为它的从库
master-data-source-name: master0
slave-data-source-names: slave0
master1: #指定master1为主库,slave1为它的从库
master-data-source-name: master1
slave-data-source-names: slave1
然后配置数据源
# 数据源 shardingsphere: datasource: names: master0,master1,slave0,slave1 #数据库db master0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.121:3306/ds0 username: root password: root master1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.121:3306/ds1 username: root password: root slave0: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.128:3306/ds0 username: root password: root slave1: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.128:3306/ds1 username: root password: root
就搞定了