【分库分表】ShardingSphere分库分表实战--inline策略

1. 概述

inline策略是简单的表达式策略,不支持范围查询

2. 测试项目介绍

在这里插入图片描述

测试项目参见配套的ShardingDemo项。首先我们对测试项目的结构做下简单的梳理:

注:
1、引入MyBatisPlus依赖,简化JDBC操作,这样我们就不需要在代码中写SQL语句了。
2、entity中的实体对象就对应数据库中的表结构。而mapper中的接口则对应JDBC操作。
3、所有操作均使用JUnit的测试案例执行。 后续所有测试操作都会配合application.properties中的配置以及JUnit测试案例进行。
4、关于ShardingSphere版本,由于目前最新的5.0版本还在孵化当中,所以我们使用已发布的4.1.1版本来进行学习。

2.1 分表不分库

我们先运行一个简单的实例,来看下ShardingJDBC是如何工作的。

工程参见 springboot 集成shardingsphere、mybatis-plus

在application01.properties配置文件中扩写application.properties文件的内容:

#配置数据源
spring.shardingsphere.datasource.names=m1

spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://10.40.65.183:3306/mock?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=
#配置真实表分布
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m1.course_$->{1..2}
#主键生成策略
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.props.worker.id=1
#配置分表策略
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid%2+1}
#其他运行属性
spring.shardingsphere.props.sql.show = true
spring.main.allow-bean-definition-overriding=true


配置分析:

  • 1、首先定义一个数据源m1,并对m1进行实际的JDBC参数配置

  • 2、spring.shardingsphere.sharding.tables.course开头的一系列属性即定义了一个名为course的逻辑表。

    actual-data-nodes属性即定义course逻辑表的实际数据分布情况,他分布在m1.course_1和m1.course_2两个表。

    key-generator属性配置了他的主键列以及主键生成策略。
    ShardingJDBC默认提供了UUID和SNOWFLAKE两种分布式主键生成策略。

    table-strategy属性即配置他的分库分表策略。分片键为cid属性。分片算法为course_$->{cid%2+1},表示按照cid模2+1的结果,然后加上前面的course__ 部分作为前缀就是他的实际表结果。注意,这个表达式计算出来的结果需要能够与实际数据分布中的一种情况对应上,否则就会报错。

    sql.show属性表示要在日志中打印实际SQL

  • 3、coursedb的表结构见示例中sql文件夹中的sql语句。

同时@PropertySource(value ={"classpath:application01.properties"})增加引用新的配置文件:

@MapperScan("com.roy.shardingDemo.mapper")
@PropertySource(value ={"classpath:application01.properties"})
@SpringBootApplication
public class ShardingJDBCApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShardingJDBCApplication.class,args);
    }
}

此时注意最好要注释掉application.properties,默认路径文件,也会自动加入配置列表中,会产生覆盖相同项,导致异常发生

package com.roy.shardingDemo;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.roy.shardingDemo.entity.Course;
import com.roy.shardingDemo.entity.Dict;
import com.roy.shardingDemo.entity.User;
import com.roy.shardingDemo.mapper.CourseMapper;
import com.roy.shardingDemo.mapper.DictMapper;
import com.roy.shardingDemo.mapper.UserMapper;
import org.apache.shardingsphere.api.hint.HintManager;
import org.junit.Test;
import org.junit.jupiter.api.Tags;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.List;


@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingJDBCTest {
    @Resource
    CourseMapper courseMapper;
    @Resource
    DictMapper dictMapper;
    @Resource
    UserMapper userMapper;

    @Test
    public void addCourse(){
        for(int i = 0 ; i < 10 ; i ++){
            Course c = new Course();
           // c.setCid(Long.valueOf(i));
            c.setCname("shardingsphere");
            c.setUserId(Long.valueOf(""+(1000+i)));
            c.setCstatus("1");
            courseMapper.insert(c);
        }
    }

    @Test
    public void queryCourse(){
        //select * from course
        QueryWrapper<Course> wrapper = new QueryWrapper<>();
        List<Course> courses = courseMapper.selectList(wrapper);
        courses.forEach(course -> System.out.println(course));
    }

我们注释掉了插入方法中的主键,使用ShardingSphere配置的主键,并且新增了一个查询的方法。

然后我们执行测试案例中的addcourse案例,执行效果,2张表各存5条数据:
在这里插入图片描述
执行后,我们可以在控制台看到很多条这样的日志:

......
2020-12-15 18:35:16.426  INFO 22412 --- [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO course  ( cname,
user_id,
cstatus )  VALUES  ( ?,
?,
? )
2020-12-15 18:35:16.427  INFO 22412 --- [           main] ShardingSphere-SQL                       : SQLStatement: InsertStatementContext(super=CommonSQLStatementContext(sqlStatement=org.apache.shardingsphere.sql.parser.sql.statement.dml.InsertStatement@1cbc5693, tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@124d26ba), tablesContext=org.apache.shardingsphere.sql.parser.binder.segment.table.TablesContext@124d26ba, columnNames=[cname, user_id, cstatus], insertValueContexts=[InsertValueContext(parametersCount=3, valueExpressions=[ParameterMarkerExpressionSegment(startIndex=59, stopIndex=59, parameterMarkerIndex=0), ParameterMarkerExpressionSegment(startIndex=62, stopIndex=62, parameterMarkerIndex=1), ParameterMarkerExpressionSegment(startIndex=65, stopIndex=65, parameterMarkerIndex=2), DerivedParameterMarkerExpressionSegment(super=ParameterMarkerExpressionSegment(startIndex=0, stopIndex=0, parameterMarkerIndex=3))], parameters=[java, 1001, 1])], generatedKeyContext=Optional[GeneratedKeyContext(columnName=cid, generated=true, generatedValues=[545674405561237505])])
2020-12-15 18:35:16.427  INFO 22412 --- [           main] ShardingSphere-SQL                       : Actual SQL: m1 ::: INSERT INTO course_2  ( cname,
user_id,
cstatus , cid)  VALUES  (?, ?, ?, ?) ::: [java, 1001, 1, 545674405561237505]
.....

从这个日志中我们可以看到,程序中执行的Logic SQL经过ShardingJDBC处理后,被转换成了Actual SQL往数据库里执行。执行的结果可以在MySQL中看到,course_1和course_2两个表中各插入了五条消息。这就是ShardingJDBC帮我们进行的数据库的分库分表操作。

在这里插入图片描述

然后,其他的几个配置文件依次对应了其他几种分库分表策略,我们可以一一演示
一下。

  • application02.properties: 分库分表示例配置。内置分片算法示例, inline、
    standard、complex、hint。广播表配置示例。
  • application03.properties: 绑定表示例配置
  • application04.properties: 读写分离示例配置

要注意理解在读写分离策略中,ShardingJDBC只能帮我们把读写操作分发到不同的数据库上,而数据库之间的数据同步,还是需要由MySQL主从集群来完成。

2.1.1 查询操作

执行ShardingJDBCTest.queryCourse(),能看到10条数据。

2.2 分库分表

我们基于2.3.1章节改造,这次,提供2个数据源,对应2个库:

#配置多个数据源
spring.shardingsphere.datasource.names=m1,m2

spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://10.40.65.183:3306/mock?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=

spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://10.40.65.183:3306/mock2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=
#真实表分布,分库,分表
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}

spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.props.worker.id=1
#inline分片策略
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid%2+1}
#inline分库策略
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{cid%2+1}

在这里插入图片描述
清空之前的数据表,并创建mock2库,并初始化2个表 course_1和course_2,现在总共有2个库:

在这里插入图片描述
执行插入操作:
在这里插入图片描述
从结果来看,这种分库分表是不均的,只是演示分表分库的用法而已。

2.2.1 查询全部操作

不带任何条件的查询,结果是全部数据,跨所有的分区

执行ShardingJDBCTest.queryCourse(),能看到10条数据:

Course{cid=634866378486059008, cname='shardingsphere', userId=1000, cstatus='1'}
Course{cid=634866379798876160, cname='shardingsphere', userId=1002, cstatus='1'}
Course{cid=634866379912122368, cname='shardingsphere', userId=1004, cstatus='1'}
Course{cid=634866380042145792, cname='shardingsphere', userId=1006, cstatus='1'}
Course{cid=634866380155392000, cname='shardingsphere', userId=1008, cstatus='1'}
Course{cid=634866379731767297, cname='shardingsphere', userId=1001, cstatus='1'}
Course{cid=634866379849207809, cname='shardingsphere', userId=1003, cstatus='1'}
Course{cid=634866379979231233, cname='shardingsphere', userId=1005, cstatus='1'}
Course{cid=634866380100866049, cname='shardingsphere', userId=1007, cstatus='1'}
Course{cid=634866380197335041, cname='shardingsphere', userId=1009, cstatus='1'}

2.2.2 排序查询

按user_Id降序排序:

    @Test
    public void queryCourse(){
        //select * from course
        QueryWrapper<Course> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("user_Id");
        List<Course> courses = courseMapper.selectList(wrapper);
        courses.forEach(course -> System.out.println(course));
    }
Course{cid=634866380197335041, cname='shardingsphere', userId=1009, cstatus='1'}
Course{cid=634866380155392000, cname='shardingsphere', userId=1008, cstatus='1'}
Course{cid=634866380100866049, cname='shardingsphere', userId=1007, cstatus='1'}
Course{cid=634866380042145792, cname='shardingsphere', userId=1006, cstatus='1'}
Course{cid=634866379979231233, cname='shardingsphere', userId=1005, cstatus='1'}
Course{cid=634866379912122368, cname='shardingsphere', userId=1004, cstatus='1'}
Course{cid=634866379849207809, cname='shardingsphere', userId=1003, cstatus='1'}
Course{cid=634866379798876160, cname='shardingsphere', userId=1002, cstatus='1'}
Course{cid=634866379731767297, cname='shardingsphere', userId=1001, cstatus='1'}
Course{cid=634866378486059008, cname='shardingsphere', userId=1000, cstatus='1'}

2.2.3 条件过滤查询

从之前的库中找一条数据,这里用634866380100866049L,注意后面的L,表示long类型数字:

    @Test
    public void queryCourse(){
        //select * from course
        QueryWrapper<Course> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("user_Id");
        wrapper.eq("cid",634866380100866049L);

        List<Course> courses = courseMapper.selectList(wrapper);
        courses.forEach(course -> System.out.println(course));
    }
2021-08-18 22:14:22.193  INFO 8252 --- [           main] ShardingSphere-SQL                       : Actual SQL: m2 ::: SELECT  cid,cname,user_id,cstatus  FROM course_2  
 WHERE  cid = ? ORDER BY user_Id DESC ::: [634866380100866049]
Course{cid=634866380100866049, cname='shardingsphere', userId=1007, cstatus='1'}

查询过程显示 仅查询了course_2,因为通过cid计算了路由,只会从course_2中查询:
在这里插入图片描述

2.2.4 范围查询

从结果集中选择2个id,进行范围查询:

    @Test
    public void queryOrderRange(){
        //select * from course
        QueryWrapper<Course> wrapper = new QueryWrapper<>();
        wrapper.between("cid",634866379849207809L,634866380100866049L);

        List<Course> courses = courseMapper.selectList(wrapper);
        courses.forEach(course -> System.out.println(course));
    }

结果报错,提示不支持范围查询:

Caused by: java.lang.IllegalStateException: Inline strategy cannot support this type sharding:RangeRouteValue(columnName=cid, tableName=course, valueRange=[634866379849207809634866380100866049])
	at com.google.common.base.Preconditions.checkState(Preconditions.java:173)
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 引入依赖 在 `pom.xml` 中引入 `shardingsphere-jdbc-core` 依赖: ```xml <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core</artifactId> <version>5.0.0-alpha</version> </dependency> ``` 2. 配置数据源 在 `application.yml` 中配置数据源: ```yaml spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: root password: root sharding: jdbc: # 数据源列表 datasource: ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test0?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: root password: root ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 username: root password: root # 分片规则配置 sharding: default-data-source: ds0 # 默认数据源 tables: user: actual-data-nodes: ds${0..1}.user_${0..1} # 实际数据节点 database-strategy: inline: sharding-column: id # 分片键 algorithm-expression: ds${id % 2} # 分库算法 table-strategy: inline: sharding-column: id # 分片键 algorithm-expression: user_${id % 2} # 分表算法 ``` 3. 编写代码 ```java @Service public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; @Override public void addUser(User user) { String sql = "INSERT INTO user (id, name) VALUES (?, ?)"; Object[] params = new Object[] { user.getId(), user.getName() }; int count = jdbcTemplate.update(sql, params); System.out.println("插入 " + count + " 条记录"); } @Override public List<User> getUsers() { String sql = "SELECT * FROM user"; return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); } } ``` 4. 测试 编写测试方法: ```java @SpringBootTest class UserServiceImplTest { @Autowired private UserService userService; @Test void addUser() { User user = new User(); user.setId(1L); user.setName("张三"); userService.addUser(user); } @Test void getUsers() { List<User> users = userService.getUsers(); System.out.println(users); } } ``` 执行测试方法,查看控制台输出和数据库表中的数据,验证分库分表是否成功实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值