ShardingSphere-JDBC入门使用

什么是ShardingSphere?

Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。 它主要由ShardingSphere-JDBC和ShardingSphere-Proxy两个可独立使用、也可混合使用的产品组成。

官网

什么是ShardingSphere-Proxy?

ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。 目前提供 MySQL 和 PostgreSQL 协议,透明化数据库操作,对 DBA 更加友好。

  • 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用;
  • 兼容 MariaDB 等基于 MySQL 协议的数据库,以及 openGauss 等基于 PostgreSQL 协议的数据库;
  • 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端,如:MySQL Command Client, MySQL Workbench, Navicat 等。

什么是ShardingSphere-JDBC?

ShardingSphere-JDBC 定位为轻量级 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. 创建Spring Boot项目,引入MySQL、ShardingSphere-JDBC等依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.1.2</version>
    </dependency>

    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    <version>8.0.28</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.22</version>
    </dependency>
  2. 创建若干张表,根据表创建controller、service、dao等代码

使用ShardingSphere-JDBC进行读写分离

  1. 数据库:master、slave;表:t_user

  2. 创建application.properties作为基础配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server.port=9090

    # 使用读写分离配置文件
    spring.profiles.active=separate

    spring.application.name=demo

    mybatis.mapper-locations=classpath*:**/sql/*.xml

    # 打印sql日志
    spring.shardingsphere.props.sql-show=true
  3. 创建application-separate.properties作为读写分离配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    # 数据源名称
    spring.shardingsphere.datasource.names=master,slave

    # 数据源1(主数据源)
    spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.master.url=jdbc:mysql://host1:3306/practice?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.shardingsphere.datasource.master.username=root
    spring.shardingsphere.datasource.master.password=password

    # 数据源2(从数据源)
    spring.shardingsphere.datasource.slave.type=com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.slave.url=jdbc:mysql://host2:3306/practice?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.shardingsphere.datasource.slave.username=root
    spring.shardingsphere.datasource.slave.password=password

    # spring.shardingsphere.rules.readwrite-splitting.data-sources.<readwrite-splitting-data-source-name>.type=Static
    # 读写分离类型,如: Static,Dynamic
    spring.shardingsphere.rules.readwrite-splitting.data-sources.my_ds.type=Static
    # 写数据源名称
    spring.shardingsphere.rules.readwrite-splitting.data-sources.my_ds.props.write-data-source-name=master
    # 读数据源名称,多个从数据源用逗号分隔
    spring.shardingsphere.rules.readwrite-splitting.data-sources.my_ds.props.read-data-source-names=slave
    # 负载均衡算法名称
    # 自定义轮询算法名称
    spring.shardingsphere.rules.readwrite-splitting.data-sources.my_ds.load-balancer-name=round_alg
    # 自定义随机算法名称
    #spring.shardingsphere.rules.readwrite-splitting.data-sources.my_ds.load-balancer-name=random_alg
    # 自定义权重算法名称
    #spring.shardingsphere.rules.readwrite-splitting.data-sources.my_ds.load-balancer-name=weight_alg

    # spring.shardingsphere.rules.readwrite-splitting.load-balancers.<load-balance-algorithm-name>.type= # ????????
    # 负载均衡算法类型
    # 使用轮询算法
    spring.shardingsphere.rules.readwrite-splitting.load-balancers.round_alg.type=ROUND_ROBIN
    # 负载均衡算法属性配置
    # 权重算法配置
    #spring.shardingsphere.rules.readwrite-splitting.load-balancers.weight_alg.props.slave1=1
    #spring.shardingsphere.rules.readwrite-splitting.load-balancers.weight_alg.props.slave2=2
  4. 分别测试查询和插入接口,查看数据源是否正常路由

    • 查询sql路由到从数据源

    • 写入sql路由到主数据源

使用ShardingSphere-JDBC进行分库分表

  1. 数据库:ds_0、ds_1;表:t_order_0/1(ds_0/1)、t_order_item_0/1(ds_0/1)

  2. 创建application.properties作为基础配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server.port=9090

    spring.profiles.active=horizontal

    spring.application.name=demo

    mybatis.mapper-locations=classpath*:**/sql/*.xml

    # 打印sql日志
    spring.shardingsphere.props.sql-show=true
  3. 创建application-horizontal.properties作为读写分离配置文件

    分库分表策略:

    分库:订单表根据id对2取模,结果为0,路由到ds_0数据库;结果为1,路由到ds_1数据库。订单明细表根据order_id对2取模,结果为0,路由到ds_0数据库;结果为1,路由到ds_1数据库,即和订单表保持一致

    分表:订单表、订单明细表根据user_id对2取模,结果为0,路由到t_order_0t_order_item_0;结果为1,路由到t_order_1t_order_item_1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    # 数据源名称
    spring.shardingsphere.datasource.names=ds_0,ds_1

    # 数据源1
    spring.shardingsphere.datasource.ds_0.type=com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.ds_0.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds_0.url=jdbc:mysql://host1:3306/practice?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.shardingsphere.datasource.ds_0.username=root
    spring.shardingsphere.datasource.ds_0.password=password

    spring.shardingsphere.datasource.ds_1.type=com.alibaba.druid.pool.DruidDataSource
    spring.shardingsphere.datasource.ds_1.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds_1.url=jdbc:mysql://host2:3306/practice?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.shardingsphere.datasource.ds_1.username=root
    spring.shardingsphere.datasource.ds_1.password=password

    # 数据节点,由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点
    spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds_$->{[0,1]}.t_order_$->{0..1}
    spring.shardingsphere.rules.sharding.tables.t_order_item.actual-data-nodes=ds_$->{[0,1]}.t_order_item_$->{0..1}

    # 分库策略
    # 订单表根据id对2取模,路由到ds_0或ds_1数据库
    spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=id
    spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_id
    # 订单明细表根据order_id对2取模,路由到ds_0或ds_1数据库,和订单表保持一致
    spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-column=order_id
    spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-algorithm-name=alg_inline_order_id

    # 分表策略
    # 订单表根据user_id对2取模,路由到t_order_0或t_order_1表
    spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=user_id
    spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_mod_id
    # 订单明细表根据user_id对2取模,路由到t_order_item_0或t_order_item_1表
    spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-column=user_id
    spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-algorithm-name=alg_mod_id

    # 分片算法
    # 订单表,行表达式分片算法
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_id.type=INLINE
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_id.props.algorithm-expression=ds_$->{id % 2}
    # 订单表、订单明细表,取模分片算法
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod_id.type=MOD
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod_id.props.sharding-count=2
    # 订单明细表,行表达式分片算法
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_order_id.type=INLINE
    spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_order_id.props.algorithm-expression=ds_$->{order_id % 2}

    # 绑定表:分片规则一致的一组分片表。使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率
    spring.shardingsphere.rules.sharding.binding-tables[0]=t_order,t_order_item
  4. 编写插入订单和订单行的代码,查看分库分表策略是否生效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Override
    public void mockInsert() {
    OrderDO order1 = new OrderDO().setId(snowflake.nextId())
    .setOrderNo(snowflake.nextIdStr())
    .setUserId(202001L)
    .setCreateTime("2023-08-12 17:35:11")
    .setCreateTime("2023-08-12 17:35:11")
    .setPayAmount(new BigDecimal("10889.0"));
    OrderItemDO orderItem1 = new OrderItemDO().setId(snowflake.nextId())
    .setOrderId(order1.getId())
    .setUserId(202001L)
    .setProductName("MacBook Pro")
    .setAmount(new BigDecimal("10889.0"));

    orderMapper.insert(order1);
    orderItemService.insert(orderItem1);

    OrderDO order2 = new OrderDO().setId(snowflake.nextId())
    .setOrderNo(snowflake.nextIdStr())
    .setUserId(202000L)
    .setCreateTime("2023-08-12 17:41:54")
    .setCreateTime("2023-08-12 17:41:54")
    .setPayAmount(new BigDecimal("3899.0"));
    OrderItemDO orderItem2 = new OrderItemDO()
    .setId(snowflake.nextId())
    .setOrderId(order2.getId())
    .setUserId(202000L)
    .setProductName("iWatch S8")
    .setAmount(new BigDecimal("3899.0"));

    orderMapper.insert(order2);
    orderItemService.insert(orderItem2);
    }
  5. 查看分库分表情况

    • order_id为偶数、user_id为奇数

    • order_id为偶数、user_id为偶数