• ShardingSphere-JDBC分库分表实现


    1. 前言

    ShardingSphere-JDBC 是 Apache ShardingSphere 的第一个产品,也是 Apache ShardingSphere 的前身。 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

    • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
    • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
    • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。

    下面基于ShardingSphere-JDBC实现分库分表。框架基于SpringBoot2.1.2.RELEASE,结合 shardingsphere和 mybatis-plus实现。

    2. 分库分表实现

    2.1 数据库搭建

    user_db_1(ds0)    
      ├── user_0     
      └── user_1     
    user_db_2(ds1)    
      ├── user_0     
      └── user_1 

     数据库user_db_1(别名:ds0)

    CREATE DATABASE /*!32312 IF NOT EXISTS*/`user_db_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
    
    USE `user_db_1`;
    
    /*Table structure for table `user_0` */
    
    DROP TABLE IF EXISTS `user_0`;
    
    CREATE TABLE `user_0` (
      `id` bigint(20) NOT NULL,
      `name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    /*Table structure for table `user_1` */
    
    DROP TABLE IF EXISTS `user_1`;
    
    CREATE TABLE `user_1` (
      `id` bigint(20) NOT NULL,
      `name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

     数据库user_db_2(别名:ds1)

    CREATE DATABASE /*!32312 IF NOT EXISTS*/`user_db_2` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
    
    USE `user_db_2`;
    
    /*Table structure for table `user_0` */
    
    DROP TABLE IF EXISTS `user_0`;
    
    CREATE TABLE `user_0` (
      `id` bigint(20) NOT NULL,
      `name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    /*Table structure for table `user_1` */
    
    DROP TABLE IF EXISTS `user_1`;
    
    CREATE TABLE `user_1` (
      `id` bigint(20) NOT NULL,
      `name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    数据库结构和表结构需要保持一致:

    2.2 搭建工程

    2.2.1 依赖

     pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.zang</groupId>
        <artifactId>shadingjdbc</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>shadingjdbc</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.20</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.shardingsphere</groupId>
                <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
                <version>4.0.0-RC1</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>5.1.4.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>

    2.2.2 实体类

    import com.baomidou.mybatisplus.annotation.TableName;
    import com.baomidou.mybatisplus.extension.activerecord.Model;
    import lombok.Data;
    
    @Data
    @TableName("user")
    public class User extends Model<User> {
        private Long id;
        private String name;
        private Integer age;
    }

    2.2.3 dao层

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.zang.shadingjdbc.model.User;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public interface UserMapper extends BaseMapper<User> {
    }

    2.2.4 service层

    import com.baomidou.mybatisplus.extension.service.IService;
    import com.zang.shadingjdbc.model.User;
    import java.util.List;
    
    public interface UserService extends IService<User> {
        void insert(User user);
        User findById(Long id);
        List<User> findAll();
    }
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.zang.shadingjdbc.mapper.UserMapper;
    import com.zang.shadingjdbc.model.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.util.List;
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public void insert(User entity) {
            userMapper.insert(entity);
        }
    
        @Override
        public User findById(Long id) {
            return userMapper.selectById(id);
        }
    
        @Override
        public List<User> findAll() {
            return userMapper.selectList(Wrappers.<User>lambdaQuery());
        }
    
    }

    2.3 分库分表配置

    ShardingSphere-JDBC在工程中的核心就是其配置。这里使用配置文件方式实现分库以及分表。

    # 数据源 ds0,ds1
    spring.shardingsphere.datasource.names=ds0,ds1
    
    # 第一个数据库
    spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/user_db_1?characterEncoding=utf-8&&serverTimezone=GMT%2B8
    spring.shardingsphere.datasource.ds0.username=root
    spring.shardingsphere.datasource.ds0.password=123
    
    # 第二个数据库
    spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
    spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/user_db_2?characterEncoding=utf-8&&serverTimezone=GMT%2B8
    spring.shardingsphere.datasource.ds1.username=root
    spring.shardingsphere.datasource.ds1.password=123
    
    # 指定course表里面主键cid 生成策略  SNOWFLAKE
    spring.shardingsphere.sharding.tables.user.key-generator.column=id
    spring.shardingsphere.sharding.tables.user.key-generator.type=SNOWFLAKE
    
    # 水平拆分的数据库(表) 配置分库 + 分表策略 行表达式分片策略
    # 分库策略 id为偶数添加到ds0,奇数添加到ds1
    spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=id
    spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{id % 2}
    
    # 分表策略 其中user为逻辑表 分表主要取决于age行
    spring.shardingsphere.sharding.tables.user.actual-data-nodes=ds$->{0..1}.user_$->{0..1}
    spring.shardingsphere.sharding.tables.user.table-strategy.inline.sharding-column=age
    # 分片算法表达式 age为偶数时分配到user_0,奇数时分配到user_1
    spring.shardingsphere.sharding.tables.user.table-strategy.inline.algorithm-expression=user_$->{age % 2}
    
    # 打开SQL输出日志
    spring.shardingsphere.props.sql.show=true
    # 一个实体类对应两张表,覆盖
    spring.main.allow-bean-definition-overriding=true

    部分说明:

    逻辑表 user

    水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:用户数据根据主键尾数拆分为2张表,分别是user0到user1,他们的逻辑表名为user。

    真实表

        在分片的数据库中真实存在的物理表。即上个示例中的user0到user1

    分片算法:

        Hint分片算法
        对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

    分片策略:

        行表达式分片策略 对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: user$->{id % 2} 表示user表根据id模2,而分成2张表,表名称为user0到user_1。

    自增主键生成策略

        通过在客户端生成自增主键替换以数据库原生自增主键的方式,做到分布式主键无重复。 采用UUID.randomUUID()的方式产生分布式主键。或者 SNOWFLAKE

    2.4 单元测试

    这里使用单元测试来模拟业务调用。

    import com.zang.shadingjdbc.model.User;
    import com.zang.shadingjdbc.service.UserService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.List;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ShadingjdbcApplicationTests {
    
        @Autowired
        private UserService userService;
        @Test
        public void addUserDb() {
            for (int i = 21; i < 40; i++) {
                User user = new User();
                user.setName("zhangsan"+i);
                user.setAge(i);
                userService.insert(user);
            }
        }
    
        @Test
        public void findAllUser() {
          List<User> userList = userService.findAll();
            System.out.println(userList.size());
        }
    
        @Test
        public void findUser() {
            User user = userService.findById(1346047719665295361L);
            System.out.println(user.getName());
        }
    }

    2.4.1 数据存储

    执行单元测试的 addUserDb方法,查看数据是按照配置的策略进行存储。

     2.4.2 单个数据查询

    执行单元测试的 findUser方法,可以看到其根据分库策略匹配到ds1库,对库中的表进行查询;因为分表字段age没有传入,所以没有定位到ds1中的表,查询执行了两次。

     2.4.3 查询所有数据

    执行单元测试的 findAllUser方法,可以看到其将所有库表都查询一遍。

     以上,即实现了简单的分库分表。

  • 相关阅读:
    《重构》读书笔记
    每周总结
    《修改代码的艺术》读书笔记
    每周总结
    每周总结
    《修改代码的艺术》读书笔记
    每周总结
    第二周周总结
    以淘宝网为例,描绘质量属性的六个常见属性场景
    机器学习第八讲
  • 原文地址:https://www.cnblogs.com/zjfjava/p/14226543.html
Copyright © 2020-2023  润新知