简介
ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
ShardingSphere 定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与 NoSQL 和 NewSQL 是并存而非互斥的关系。NoSQL 和 NewSQL 作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
ShardingSphere 已经在2020年4月16日从Apache孵化器毕业,成为 Apache 顶级项目。
快速入门
创建表
这里需要注意的是字段不要有关键字子类的,尽量长一点。
create database `cloud-order`; use `cloud-order`; create database if not exists `cloud-order` default charset utf8mb4 collate utf8mb4_general_ci; create table t_order_1 ( `oid` bigint(20) unsigned not null auto_increment comment '主键id', `order_code` varchar(64) not null default '' comment '订单号', primary key (`oid`), key `idx_order_code` (`order_code`) ) comment '订单表'; create table t_order_2 ( `oid` bigint(20) unsigned not null auto_increment comment '主键id', `order_code` varchar(64) not null default '' comment '订单号', primary key (`oid`), key `idx_order_code` (`order_code`) )comment '订单表';
添加依赖
我说下项目的基本情况:
- spring boot 2.6.5
- spring cloud 2021.0.1
- mybatis-spring-boot-starter 2.2.2
- sharding-jdbc-spring-boot-starter 4.1.1
目前来说都是比较新的版本,所以肯定会踩很多的坑。具体的配置如下(由于我的配置是之前的项目修改,不够简洁大家凑合看吧):
plugins { id 'java' id 'org.springframework.boot' version '2.6.5' id 'io.spring.dependency-management' version '1.0.11.RELEASE' } group 'io.zhengsh' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenLocal() maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' } mavenCentral() } ext { set('springCloudVersion', "2021.0.1") } configurations { compile.exclude module: 'spring-boot-starter-tomcat' } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-undertow' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j' // mysql implementation 'mysql:mysql-connector-java' // mybatis implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2' // shardingsphere implementation 'org.apache.shardingsphere:sharding-jdbc-spring-boot-starter:4.1.1' testImplementation 'org.springframework.boot:spring-boot-starter-test' compileOnly 'org.projectlombok:lombok:1.18.22' annotationProcessor 'org.projectlombok:lombok:1.18.22' testCompileOnly 'org.projectlombok:lombok:1.18.22' testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' } dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } } tasks.named('test') { useJUnitPlatform() }
添加 YML 配置
添加 YML 配置,主要是有一下配置
- 设置数据源,就是配置有哪些库
- 设置数据表,参与分片的表
- 设置分表的策略,我这边使用的是雪花算法生成 id , 并且通过取模的方式指定数据位置。
spring: shardingsphere: # 设置数据源 datasource: names: o1 o1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/cloud-order?useUnicode=true&zeroDateTimeBehavior=convertToNull&useSSL=false username: root password: root123 props: # 打印 sql sql.show: true sharding: default-data-source-name: o1 tables: t_order: # 指定需要分的表 # 表达式, 实际数据节点: 根据上一个节点找到此值, {0..1}为groovy语言,$会替换成{0..1}的一个值,数据库表是: t_order_1 , t_order_2 # 这个配置是告诉sharding有多少个表 actual-data-nodes: o1.t_order_$->{1..2} key-generator: # 指定主键 column: oid # 生成方式: 雪花算法 type: snowflake props: worker: id: 1 # 分表策略 table-strategy: inline: # 配置sharding的计算列 sharding-column: oid # 配置sharding的表达式,对应的id必须和sharding-column的值对应,否则报错 algorithm-expression: t_order_$->{oid % 2 + 1}
Java 代码
1、实体类
public class OrderEntity { private Long oid; private String orderCode; // 此处省略 get、set 方法 }
2、Mapper 文件
@Mapper public interface OrderEntityMapper { @Insert("insert into o1.t_order (`order_code`) values(#{orderCode})") @Options(useGeneratedKeys = true, keyProperty = "oid", keyColumn = "oid") int insertSelective(OrderEntity record); @Select("select * from o1.t_order where oid = #{oid}") OrderEntity selectOne(@Param("oid") Long oid); }
3、 单元测试
@Test public void insertOrder() { for (int i = 0; i < 10; i++) { OrderEntity order = new OrderEntity(); order.setOrderCode("OR000" + i); int row = orderEntityMapper.insertSelective(order); Assert.assertTrue(row > 0); } } @Test public void selectOne() { OrderEntity order1 = orderEntityMapper.selectOne( 715721929377452033L); Assert.assertNotNull(order1); }
执行结果
1、插入数据
2、查询结果
常见问题
健康检测出错
核心就是 yml 配置。我之前遇到一个问题就是健康检测出错,无法配置,所以采用 Java config 方式的配置来规避这个启动报错
添加 Java config 配置如下所示:
@Configuration public class DataSourceConfig { @Bean public HealthIndicator dbHealthIndicator(DataSource dataSource) { DataSourceHealthIndicator indicator = new DataSourceHealthIndicator(dataSource); indicator.setQuery("select 1"); return indicator; } }
空指针异常
MyBatis 查询时,报错了,报了一个空指针。查询别的表的时候不报错,就这个表报错,如下所示:
### Cause: java.lang.NullPointerException at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:149) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76) 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.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426) ... 75 common frames omitted Caused by: java.lang.NullPointerException: null at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visit(AbstractParseTreeVisitor.java:18) at org.apache.shardingsphere.sql.parser.mysql.visitor.impl.MySQLDMLVisitor.createProjection(MySQLDMLVisitor.java:446) // 省略部分异常
最开始以为是分表的原因,后来查了网上的资料,是表里面有关键字导致的。查看异常信息时,忽略了最上面的提示:
line 1:176 mismatched input 'order' expecting {..... 忽略一些关键字}
invoke method mod() on null object 异常
后面我又遇到了这个问题 Cannot invoke method mod() on null object] with root cause
异常,完整的错误信息如下:
### SQL: insert into o1.t_order (`order_code`, `code`) values(?, ?) ### Cause: java.lang.NullPointerException: Cannot invoke method mod() on null object at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441) at com.sun.proxy.$Proxy112.insert(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:272) // 省略部分异常
问题原因:分片键和分片策略中的字段不一致导致