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方法,可以看到其将所有库表都查询一遍。

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

posted @ 2021-01-04 09:57  雪山上的蒲公英  阅读(1086)  评论(0编辑  收藏  举报
/* 返回顶部代码 */