跟我学ShardingSphere之SpringBoot + ShardingJDBC分库示例

ShardingSphere

Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

以上是ShardingSphere官方的介绍,其实说得简单点就是ShardingSphere为我们提供了一整套数据库扩展解决方案,让我们可以很方便进行表的扩展、库的扩展。

在互联网场景下,我们关系型数据库中存储的数据量会很大,如果不进行表的拆分、库的拆分,会导致单表的数据量巨大,一方面单库的存储限制,另一方面单表数据量大会出现性能瓶颈,所以一般常见的解决方案就是进行分库、分库,那分库、分表会带来一些问题,比如

  • 将一个表拆成多个表,写入一条数据该写到哪个表?
  • 表拆分后,该如何去查询数据?

ShardingSphere提供了对应的解决方案,我们先来看看ShardingJDBC是如何解决的

ShardingJDBC

ShardingJDBC定位为轻量的Java框架,相当于是对JDBC功能的增强,以Jar包的形式提供服务,完全兼容 JDBC 和各种 ORM 框架
在这里插入图片描述

如上图所示,是整个ShardingJDBC的调用结构图,ShardingJDBC是嵌入到业务代码层,相当于接管了原来JDBC的功能,数据库相关的操作全部由ShardingJDBC来完成。

以上是ShardingJDBC简单的介绍,下面通过一个例子来看看如何使用ShardingJDBC

简单的示例

SpringBoot2.4.3 + mybatis-plus3.1.1 + ShardingJDBC4.1.1

表结构

CREATE TABLE `tb_user` (
  `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `user_name` varchar(20) NOT NULL COMMENT '用户名称',
  `user_address` varchar(100) NOT NULL COMMENT '用户地址',
  `age` int(11) NOT NULL COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=230 DEFAULT CHARSET=utf8 COMMENT='用户信息表';

Maven依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath />
    </parent>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--Mybatis-Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!-- for spring boot -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>

        <!-- for spring namespace -->
        <!--<dependency>-->
            <!--<groupId>org.apache.shardingsphere</groupId>-->
            <!--<artifactId>sharding-jdbc-spring-namespace</artifactId>-->
            <!--<version>4.1.1</version>-->
        <!--</dependency>-->
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy</artifactId>
            <version>3.0.6</version>
        </dependency>

    </dependencies>

User实体

@Data
@TableName("tb_user")
public class User {
    @TableId("user_id")
    private Integer userId;

    @TableField("user_name")
    private String userName;

    @TableField("user_address")
    private String userAddress;

    @TableField("age")
    private Integer age;
}

Mapper和Service

public interface UserMapper extends BaseMapper<User> {

}
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void insert(User user){
        int count = userMapper.insert(user);
        System.out.println("insert count:" + count);
    }

    public User getUser(Long userId){
        return userMapper.selectById(userId);
    }
}

配置文件

server:
  port: 8090

spring:
  main:
    allow-bean-definition-overriding: true

  shardingsphere:
    datasource:
      names: testdb0,testdb1
      testdb0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/testdb0?useUnicode=true&characterEncoding=utf-8
        username: root
        password: 123456
      testdb1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/testdb1?useUnicode=true&characterEncoding=utf-8
        username: root
        password: 123456

    # 分库配置
    sharding:
      tables:
        tb_user:  # 分库的表名
          actual-data-nodes: testdb$->{0..1}.tb_user    # 分片数据节点配置,Grovvy表达式,表示testdb0、testdb1
          # 分库配置
          database-strategy:  # 分库策略配置
            inline:  # inline表示行表达式策略,还有其他分片策略
              sharding-column: user_id   # tb_user表的分片字段
              algorithm-expression: testdb$->{user_id % 2}   # 分片策略,表示user_id对2取模进行分片

    props:
      sql.show: true  # 控制台打印SQL

在ShardingJDBC中分库、分表都是需要指定表名,因为我们只需要对数据量大的表进行扩展,不可能对所有的表进行拆分

5、Controller

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/user/add")
    public String addUser(@RequestBody User user){
        userService.insert(user);
        return "ok";
    }

    @RequestMapping("/user/get")
    @ResponseBody
    public User getUser(Long userId){
        return userService.getUser(userId);
    }
}

6、启动类

@SpringBootApplication
@MapperScan("com.kxg.mapper")
public class ShardingSphereMain {
    public static void main(String[] args) {
        SpringApplication.run(ShardingSphereMain.class,args);
    }
}

启动项目,会看到如下信息:

2021-08-23 20:16:24.920  INFO 8928 --- [           main] o.a.s.core.log.ConfigurationLogger       : ShardingRuleConfiguration:
tables:
  tb_user:
    actualDataNodes: testdb$->{0..1}.tb_user
    databaseStrategy:
      inline:
        algorithmExpression: testdb$->{user_id % 2}
        shardingColumn: user_id
    logicTable: tb_user

2021-08-23 20:16:24.922  INFO 8928 --- [           main] o.a.s.core.log.ConfigurationLogger       : Properties:
sql.show: 'true'

表示你的配置已经生效了,下面我们来验证一下分库的效果
在这里插入图片描述

在这里插入图片描述

我们新增了两个用户,user_id分别为1和2,我们来看看实际插入数据库中的情况

在这里插入图片描述

可以看到,是按照user_id取模插入到不同的数据库中的,是我们预期的结果。再看看控制台打印的SQL

Logic SQL: INSERT INTO tb_user  ( user_id,user_name,user_address,age )  VALUES  ( ?,?,?,? )
/****************** 中间省略若干行 *************************************/
Actual SQL: testdb1 ::: INSERT INTO tb_user  ( user_id,user_name,user_address,age )  VALUES  (?, ?, ?, ?) ::: [1, 李四, 武汉市, 22]

---------------------------------------华丽的分隔线---------------------------------------------------------------

Logic SQL: INSERT INTO tb_user  ( user_id,user_name,user_address,age )  VALUES  ( ?,?,?,? )
Actual SQL: testdb0 ::: INSERT INTO tb_user  ( user_id,user_name,user_address,age )  VALUES  (?, ?, ?, ?) ::: [2, 张三, 武汉市, 18]

再来看看查询
在这里插入图片描述

后台打印的SQL如下:

Logic SQL: SELECT user_id,user_name,user_address,age FROM tb_user WHERE user_id=? 
 Actual SQL: testdb0 ::: SELECT user_id,user_name,user_address,age FROM tb_user WHERE user_id=?  ::: [2]

从上面打印的SQL结果可以看出来,每次请求分成逻辑SQL(Logic SQL)和实际执行的SQL(Actual SQL),上面的例子中,我们实际只发送了两次新增用户的请求,控制台实际上打印了4条SQL

逻辑SQL是客户端发过来的真实SQL,实际执行的SQL是数据库实际执行的SQL,在以往普通项目中这两种SQL是同一个,但是在分库、分表场景下,这两个SQL却是不同的。因为当你新增一个用户时,客户端是不关心你到底数据存在哪个库哪张表的,它发过来的SQL就是逻辑SQL,服务端需要关心数据存储在哪里,所以这里使用ShardingJDBC来做了一次转换,将逻辑SQL转换成实际可以执行的SQL

以上就是简单的分库实例,当然实际的业务场景不可能这么简单,涉及到多张表的关联查询,在进行分库或分表之前一定要做好分片字段的设计,如果设计不合理不但达不到预期的效果,还可能会适得其反

如果感觉对你有些帮忙,请收藏好,你的关注和点赞是对我最大的鼓励!
如果想跟我一起学习,坚信技术改变世界,请关注【Java天堂】公众号,我会定期分享自己的学习成果,第一时间推送给您

在这里插入图片描述

posted @ 2021-08-23 23:45  果子爸聊技术  阅读(34)  评论(0编辑  收藏  举报