Sharding-jdbc入门学习笔记

Sharding-jdbc入门学习笔记

文章目录

  • Sharding-jdbc入门学习笔记
  • 一, Sharding-jdbc概念
    • 1.1 ShardingSphere簡介
    • 1.2 Sharding-jdbc簡介
      • 1.2.1 SQL
      • 1.2.2 分片
      • 1.2.3 改寫
      • 1.2.4 分佈式主鍵生出器
  • 二, 讀寫分離
    • 2.1 前期準備
    • 2.2 依賴文件
    • 2.3 配置文件
    • 2.4 啟動類
    • 2.5 MVC類
      • 2.5.1 模型層
      • 2.5.2 DAO層
      • 2.5.3 Service層
      • 2.5.4 Controller層
    • 2.6 配置類
      • 2.6.1 數據庫參數類
      • 2.6.2 讀寫分離配置類
    • 2.7 測試
    • 2.8 自定義訪問策略
  • 三, 分庫分表
    • 3.1 前期準備
    • 3.2 依賴文件
    • 3.3 配置文件
    • 3.4 啟動類
    • 3.5 MVC類
      • 3.5.1 模型層
      • 3.5.2 Mapper層
      • 3.5.3 Service
      • 3.5.4 Controller
    • 3.6 配置類
      • 3.6.1 自定義主鍵生成器
      • 3.6.2 數據庫自增主鍵生成器
    • 3.7 測試

​ 關係型數據庫在單表數據庫量較大的情況下, 單表性能會急劇下降. 面對這個問題, 常見的做法就是進行分庫分表.

​ 分庫分表通常分為: 垂直劃分, 水平劃分. 垂直劃分通常是根據業務場景將一個多字段達標拆分成多個少字段小表; 水平劃分是根據某個分片策略將同一個表的數據分開存到多個相同結構表中.

一, Sharding-jdbc概念

1.1 ShardingSphere簡介

Sharding-jdbc是ShardingSphere的其中一個模塊, ShardingSphere是一套開源的分佈式數據庫中間件解決方案組成的生態圈, 它由Sharding-jdbc, Sharding-proxy, Sharding-sidecar(正在規劃中).它們均提供標準化的數據分片, 分佈式事務和數據庫治理功能.

1.2 Sharding-jdbc簡介

Sharding-jdbc是輕量級java框架, 在jdbc層提供的額外服務. 它使用客戶端直連數據庫, 以jar包形式提供服務, 無需額外部署和依賴, 可理解為增強版的jdbc驅動, 完全兼容jdbc和各種orm框架(jpa, mybatis, spring jdbc等), 基於任何第三方的數據庫連接池(dbcp, c3p0, druid等).

1.2.1 SQL

  • 邏輯表: 水平拆分的數據庫(表)的相同邏輯和數據結構表的總稱. 例如:用戶數據根據主鍵尾數拆分為10張表, 分別是user_0到user_9, 它們的邏輯表名為user.
  • 真實表: 在分片的數據庫中真實存在的物理表, 即上個示例中的user_0到user_9.
  • 數據節點: 數據分片的最小單元. 由數據源名稱和數據表組成, 例: ds_0.user_1
  • 綁定表:指分片規則一致的主表和子表。例如user表和orders表,均按照user_id分片,則此兩張表互為綁定表關係。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯。
  • 廣播表:指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表。

1.2.2 分片

  • 垂直分片

​ 按照業務拆分的方式稱為垂直分片,又稱為縱向拆分,它的核心理念是專庫專用。在拆分之前,一個數據庫由多個數據表構成,每個表對應著不同的業務。而拆分之後,則是按照業務將表進行歸類,分佈到不同的數據庫中,從而將壓力分散至不同的數據庫。

​ 垂直分片往往需要對架構和設計進行調整。通常來說,是來不及應對互聯網業務需求快速變化的;而且,它也無法真正解決單點瓶頸。垂直拆分可以緩解數據量和訪問量帶來的問題,但無法根治、如果垂直拆分之後,表中的數據量依然超過單節點所能承載的閾值,則需要水平分片來進一步處理。

  • 水平分片

​ 水平分片又稱為橫向拆分。相對於垂直分片,它不再將數據根據業務邏輯分類,而是通過某個字段,根據某種規則將數據分散至多個庫或表中,每個分片僅包含數據的一部分。

​ 水平分片從理論上突破了數據量處理的瓶頸,並且擴展相對自由,是分庫分表的標準解決方案。

  • 分片鍵:用於分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段。

  • 分片算法:通過分片算法將數據分片,支持通過 =,BETWEEN 和 IN 分片。目前提供四種分片算法。

    (1)精確分片算法:對應PreciseShardingAlgorithm,用於處理使用單一鍵作為分片鍵的 = 於 IN 進行分片的場景。需要配合StandShardingStrategy使用。

    (2)範圍分片算法:對應RangeShardingAlgorithm,用於處理使用單一鍵作為分片鍵的 BETWEEN AND進行分片的場景。需要配合StandardShardingStrategy使用。

    (3)複合分片算法:對應ComplexKeysShardingAlgorithm,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較複雜,需要開發者自行處理其中的複雜度。需要配合ComplexShardingStrategy使用。

    (4)Hint分片算法:對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。

1.2.3 改寫

分頁查詢

從第一條開始查詢,查找兩條

select score from t_score order by score desc limit 1, 2

t_score_0 :100,90,80

t_score_1 :95,85,75

查詢結果

t_score_0:90,80

t_score_1:85,75

歸併結果

85,80

​ 如上所示,想要取得兩個表共同的按照分數排序的第2和3條數據,應該是95和90。由於執行的SQL只能從每個表中獲取第2和3條數據,因此進行結果歸併無論怎麼實現,都不可能獲得正確的結果。

​ 正確的做法是將分頁條件改寫為LIMIT 0,3,再結合排序條件計算出正確的數據。

select score from t_score order by score desc limit 0, 3

性能瓶頸:查詢偏移量過大的分頁會導致數據庫獲取數據性能低下

select score from t_score order by score desc limit 10000, 10

改寫後:

select score from t_score order by score desc limit 0, 10010

優化:

若ID具有連續性,可以使用

select score from t_score where id > 10000 and id <=10010 order by id
或
select score from t_score where id > 10000 limit 10

1.2.4 分佈式主鍵生出器

ShardingSphere提供靈活的配置分佈式主鍵生出策略方式。在分片規則配置模塊可配置每個表的主鍵生出策略,默認使用雪花算法生出64bit的長整型數據。

雪花算法

​ 雪花算法是由Twitter公佈的分佈式主鍵生出算法,它能夠保證不同進程主鍵的不重複性,以及相同進程主鍵的有序性。

​ 在同個進程中,它首先是通過時間位保證不重複,如果時間相同則是通過序列位保證。使用雪花算法生出的主鍵,二進制表示形式包含4部分,從高位到低位分為:1bit符號位(預留的符號位,為0),41bit時間戳位,10bit工作進程位以及12bit序列號位。

工作進程位:該標誌在java進程內是唯一的,如果是分佈式應用部署應保證每個工作進程的ID是不同的。該值默認值為0,可通過調用靜態方法DefaultKeyGenerator.setWorkerId()設置。

兩種數據源工廠:

  • MasterSlaveDataSourceFactory:用於創建獨立使用讀寫分離的JDBC驅動。

    public static DataSource createDataSource(Map<String, DataSource> dataSourceMap, MasterSlaveRuleConfiguration masterSlaveRuleConfig, Map<String, Object> configMap, Properties props)
    
  • ShardingDataSourceFactory:用於創建分庫分表或分庫分表+讀寫分離的JDBC驅動

    public static DataSource createDataSource(Map<String, DataSource> dataSourceMap, ShardingRuleConfiguration shardingRuleConfig, Map<String, Object> configMap, Properties props)
    

數據源配置規則:

  • MasterSlaveRuleConfiguration:讀寫分離配置主庫,從庫和選擇從庫的策略規則

    /**
    *  name:名稱
    *  masterDataSourceName:主庫名稱
    *  slaveDataSourceNames:從庫名稱列表
    *  loadBalanceAlgorithm:從庫訪問策略
    */
    //使用默認規則(輪詢)
    MasterSlaveRuleConfiguration(String name, String masterDataSourceName, Collection<String> slaveDataSourceNames);
    //自己選擇規則
    MasterSlaveRuleConfiguration(String name, String masterDataSourceName, Collection<String> slaveDataSourceNames, MasterSlaveLoadBalanceAlgorithm loadBalanceAlgorithm);
    
  • ShardingRuleConfiguration:分庫分表配置的核心和入口,包含多個TableRuleConfiguration和MasterSlaveRuleConfiguration。每一組相同規則的表配置一個TableRuleConfiguration

    	private String defaultDataSourceName;//名稱
        private Collection<TableRuleConfiguration> tableRuleConfigs = new LinkedList();//表分片規則集合
        private Collection<String> bindingTableGroups = new LinkedList();//綁定表集合
        private ShardingStrategyConfiguration defaultDatabaseShardingStrategyConfig;//默認分庫策略配置接口
        private ShardingStrategyConfiguration defaultTableShardingStrategyConfig;//默認分表策略配置接口
        private KeyGenerator defaultKeyGenerator;//默認主鍵生成器(雪花算法)
        private Collection<MasterSlaveRuleConfiguration> masterSlaveRuleConfigs = new LinkedList();//讀寫分離,主從配置列表
    

表規則配置 TableRuleConfiguration:

    private String logicTable;//邏輯表名稱
    private String actualDataNodes;//數據節點
    private ShardingStrategyConfiguration databaseShardingStrategyConfig;//分庫策略配置接口
    private ShardingStrategyConfiguration tableShardingStrategyConfig;//分表策略配置接口
    private String keyGeneratorColumnName;//主鍵字段名稱
    private KeyGenerator keyGenerator;//主鍵生成器
    private String logicIndex;//

二, 讀寫分離

​ 使用Sharding-jdbc進行讀寫分離的時候, 只允許設置一個主庫, 從庫的話可以設置多個, 從庫訪問策略Sharding-jdbc只提供了兩種: 輪詢(ROUND_ROBIN)和隨機(RANDOM), 源碼如下:

package com.dangdang.ddframe.rdb.sharding.api.strategy.slave;

public enum MasterSlaveLoadBalanceStrategyType {
    ROUND_ROBIN(new RoundRobinMasterSlaveLoadBalanceStrategy()),
    RANDOM(new RandomMasterSlaveLoadBalanceStrategy());

    private final MasterSlaveLoadBalanceStrategy strategy;

    public static MasterSlaveLoadBalanceStrategyType getDefaultStrategyType() {
        return ROUND_ROBIN;
    }

    private MasterSlaveLoadBalanceStrategyType(MasterSlaveLoadBalanceStrategy strategy) {
        this.strategy = strategy;
    }

    public MasterSlaveLoadBalanceStrategy getStrategy() {
        return this.strategy;
    }
}

2.1 前期準備

​ 進行讀寫分離, 先創建三個數據庫(一個主庫ds_0, 兩個從庫ds_1, ds_2), 主庫進行增刪改操作, 從庫進行查詢操作. 然後在每個數據庫中都創建user表. 插入數據.

2.2 依賴文件

在項目的pom.xml中, 導入以下依賴:

<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-restartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.1.22version>
        dependency>
        <dependency>
            <groupId>com.dangdanggroupId>
            <artifactId>sharding-jdbc-coreartifactId>
            <version>1.5.4.1version>
        dependency>
        <dependency>
            <groupId>com.dangdanggroupId>
            <artifactId>sharding-jdbc-config-springartifactId>
            <version>1.5.4.1version>
        dependency>

如上所示, 數據庫使用了MySQL, orm框架使用了jpa, 數據庫連接池使用了druid.

2.3 配置文件

​ 在資源路徑創建配置文件application.yml, 配置如下:

server:
  port: 9060
spring:
  application:
    name: sharding-jdbc-demo
master:
  url: jdbc:mysql://localhost:3306/ds_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
  driverClassName: com.mysql.cj.jdbc.Driver
  databaseName: ds_0
  username: root
  password: 123456
slave1:
  url: jdbc:mysql://localhost:3306/ds_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
  driverClassName: com.mysql.cj.jdbc.Driver
  databaseName: ds_1
  username: root
  password: 123456
slave2:
  url: jdbc:mysql://localhost:3306/ds_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
  driverClassName: com.mysql.cj.jdbc.Driver
  databaseName: ds_2
  username: root
  password: 123456

2.4 啟動類

package com.lmc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ShardingJdbcApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class);
    }
}

2.5 MVC類

2.5.1 模型層

創建數據庫user表映射實體類User:

@Entity
@Component
public class User implements Serializable {
    @Id
    private Long id;
    @Column(nullable = false)
    private String city;
    @Column(nullable = false)
    private String name;

    //ignore setter/getter method
}

2.5.2 DAO層

@Repository
public interface UserDao extends JpaRepository<User, Long> {

    List<User> findAll();

    User save(User user);
}

2.5.3 Service層

UserService接口 :

public interface UserService {
    List<User> list();

    User save(User user);
}

UserServiceImpl實現類 :

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public List<User> list() {
        return userDao.findAll();
    }

    @Override
    public User save(User user) {
        return userDao.save(user);
    }
}

2.5.4 Controller層

UserController :

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/list")
    public Object list() {
        return userService.list();
    }

    @RequestMapping("/add")
    public User add() {
        User user = new User();
        user.setId(1003L);
        user.setName("lmchh");
        user.setCity("廣州");
        return userService.save(user);
    }
}

2.6 配置類

2.6.1 數據庫參數類

主庫ds_0配置類Database0Config :

package com.lmc.database;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

@Component
@ConfigurationProperties(prefix = "master")
public class Database0Config {
    private String url;
    private String username;
    private String password;
    private String databaseName;
    private String driverClassName;

    public DataSource createDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setDriverClassName(driverClassName);
        return druidDataSource;
    }
    //ignore setter/getter method
}

從庫ds_1和ds_2配置類Database1Config和Database2Config :

@Component
@ConfigurationProperties(prefix = "slave1")
public class Database1Config {
    private String url;
    private String username;
    private String password;
    private String databaseName;
    private String driverClassName;

    public DataSource createDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setDriverClassName(driverClassName);
        return druidDataSource;
    }
	//ignore setter/getter method
}
@Component
@ConfigurationProperties(prefix = "slave2")
public class Database2Config {
    private String url;
    private String username;
    private String password;
    private String databaseName;
    private String driverClassName;

    public DataSource createDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setDriverClassName(driverClassName);
        return druidDataSource;
    }
	//ignore setter/getter method
}

2.6.2 讀寫分離配置類

@Configuration
public class DataSourceConfig {

    @Autowired
    private Database0Config database0Config;
    @Autowired
    private Database1Config database1Config;
    @Autowired
    private Database2Config database2Config;

    @Bean
    public DataSource getDataSource() throws SQLException {
        return buildDataSource();
    }

    private DataSource buildDataSource() throws SQLException {
        //設置從庫數據源集合
        Map<String, DataSource> slaveDataSourceMap = new HashMap<>();
        slaveDataSourceMap.put(database1Config.getDatabaseName(), database1Config.createDataSource());
        slaveDataSourceMap.put(database2Config.getDatabaseName(), database2Config.createDataSource());
        //獲取數據源對象
        DataSource dataSource = MasterSlaveDataSourceFactory.createDataSource("masterSlave", database0Config.getDatabaseName(),
                database0Config.createDataSource(), slaveDataSourceMap, MasterSlaveLoadBalanceStrategyType.ROUND_ROBIN);
        return dataSource;
    }

}

​ 讀寫分離配置類主要是使用主從數據源工廠MasterSlaveDataSourceFactory來指定DataSource, 該Factory創建DataSource有幾種方式 :

	public static DataSource createDataSource(String name, String masterDataSourceName, DataSource masterDataSource, Map<String, DataSource> slaveDataSourceMap) throws SQLException {
        return new MasterSlaveDataSource(name, masterDataSourceName, masterDataSource, slaveDataSourceMap, MasterSlaveLoadBalanceStrategyType.getDefaultStrategyType());
    }

    public static DataSource createDataSource(String name, String masterDataSourceName, DataSource masterDataSource, Map<String, DataSource> slaveDataSourceMap, MasterSlaveLoadBalanceStrategyType strategyType) throws SQLException {
        return new MasterSlaveDataSource(name, masterDataSourceName, masterDataSource, slaveDataSourceMap, strategyType);
    }

    public static DataSource createDataSource(String name, String masterDataSourceName, DataSource masterDataSource, Map<String, DataSource> slaveDataSourceMap, MasterSlaveLoadBalanceStrategy strategy) throws SQLException {
        return new MasterSlaveDataSource(name, masterDataSourceName, masterDataSource, slaveDataSourceMap, strategy);
    }

可以看出, Factory創建DataSource跟以下參數相關 :

  • name : 數據源名稱
  • masterDataSourceName: 主庫數據源名稱
  • masterDataSource: 主數據源
  • slaveDataSourceMap: 從數據源集合
  • strategyType:訪問策略, 自帶兩種方式(ROUND_ROBIN和RANDOM), 也可以自定義策略
  • strategy: 傳入策略對象, 可使用默認的輪詢策略new RoundRobinMasterSlaveLoadBalanceStrategy()和隨機策略new RandomMasterSlaveLoadBalanceStrategy(), 也可以傳入自定義訪問策略對象

2.7 測試

讀寫分離, 讓主庫負責對數據的增刪改操作, 從庫負責查詢操作, 因此在controller提供了add()和list()方法來測試結果.

訪問 http://localhost:9060/list, 輪詢打印數據庫ds_1和ds_2中user表的數據

訪問 http://localhost:9060/add, 在數據庫ds_0的user中插入數據

2.8 自定義訪問策略

自定義訪問策略的時候, 需要實現MasterSlaveLoadBalanceStrategy接口, 重寫getDataSource()方法. 以下是自定義訪問策略, 輪詢兩次訪問

@Component
public class TwiceRobinMasterSlaveLoadBalanceStrategy implements MasterSlaveLoadBalanceStrategy {
    private static final ConcurrentHashMap<String, AtomicInteger> COUNT_MAP = new ConcurrentHashMap();
    //每個DataSource默認輪詢次數
    private static AtomicInteger DATASOURCE_ROBIN_TIME = new AtomicInteger(2);
    //當前輪詢, 從1開始
    private static AtomicInteger CURRENT_ROBIN = new AtomicInteger(1);
    //返回索引, 默認-1表示未訪問
    private static AtomicInteger ROBIN_LAST_INDEX = new AtomicInteger(-1);

    public TwiceRobinMasterSlaveLoadBalanceStrategy() {
    }

    public String getDataSource(String name, String masterDataSourceName, List<String> slaveDataSourceNames) {
        //當前輪詢小於默認輪詢次數時
        if (CURRENT_ROBIN.get() < DATASOURCE_ROBIN_TIME.get() ) {
            CURRENT_ROBIN.set(CURRENT_ROBIN.get() + 1);
            if (ROBIN_LAST_INDEX.get() == -1) {
                culculate(name, masterDataSourceName, slaveDataSourceNames);
                return slaveDataSourceNames.get(ROBIN_LAST_INDEX.get());
            }
            return slaveDataSourceNames.get(ROBIN_LAST_INDEX.get());
        }else {
            culculate(name, masterDataSourceName, slaveDataSourceNames);
            return slaveDataSourceNames.get(ROBIN_LAST_INDEX.get());
        }

    }
    
    private void culculate(String name, String masterDataSourceName, List<String> slaveDataSourceNames) {
        AtomicInteger count = COUNT_MAP.containsKey(name) ?COUNT_MAP.get(name) : new AtomicInteger(0);
        COUNT_MAP.putIfAbsent(name, count);
        count.compareAndSet(slaveDataSourceNames.size(), 0);
        CURRENT_ROBIN.set(1);
        ROBIN_LAST_INDEX.set(count.getAndIncrement() % slaveDataSourceNames.size());
    }
}

三, 分庫分表

3.1 前期準備

​ 進行分庫分表操作, 首先要提前創建多個數據庫ds0和ds1. 在每一個數據庫中創建表user_0和user_1.

注意: 盡量對數據庫和表名少使用下劃線, 否則使用yml會出現問題.

3.2 依賴文件

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.22version>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.1.0version>
        dependency>
        <dependency>
            <groupId>io.shardingspheregroupId>
            <artifactId>sharding-jdbc-spring-boot-starterartifactId>
            <version>3.0.0.M3version>
        dependency>
        <dependency>
            <groupId>io.shardingspheregroupId>
            <artifactId>sharding-jdbc-spring-namespaceartifactId>
            <version>3.1.0version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

    dependencies>

3.3 配置文件

####################################數據庫連接池配置變量####################################
#初始連接數
initial-size: 5
#最小連接數
min-idle: 5
#
max-idle: 100
#最大連接池數量
max-active: 20
#配置獲取連接等待超時的時間
max-wait: 60000
#配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
time-between-eviction-runs-millis: 60000
#配置一個連接在池中最小生存的時間,單位是毫秒
min-evictable-idle-time-millis: 300000


sharding:
  #數據庫配置
  jdbc:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ds0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        initial-size: ${initial-size}
        min-idle: ${min-idle}
        max-active: ${max-active}
        validation-query: SELECT 1 FROM DUAL
        time-between-eviction-runs-millis: ${time-between-eviction-runs-millis}
        min-evictable-idle-time-millis: ${min-evictable-idle-time-millis}
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ds1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        initial-size: ${initial-size}
        min-idle: ${min-idle}
        max-active: ${max-active}
        validation-query: SELECT 1 FROM DUAL
        time-between-eviction-runs-millis: ${time-between-eviction-runs-millis}
        min-evictable-idle-time-millis: ${min-evictable-idle-time-millis}
    #分庫分表配置
    config:
      sharding:
        default-database-strategy:
          inline:
            sharding-column: user_id  #數據庫分片列名稱
            algorithm-expression: ds$->{user_id % 2} #分庫算法表達式(取模, hash, 分塊等)
        tables:
          #用戶表的配置信息
          user:
            actual-data-nodes: ds$->{0..1}.user_$->{0..1}  #真實數據節點,由數據源名 +表名組成,以小數點分隔,多個表以逗號分隔,支持inline表達式
            table-strategy:
              inline:
                sharding-column: user_id  #分表策略
                algorithm-expression: user_$->{(user_id % 5) % 2} #分表策略,這裡不能跟分庫策略一樣,否則會導致一半數據沒有機會插入表
            key-generator-column-name: user_id  #配置自動生成主鍵
            key-generator-class-name: com.lmc.subtab.utils.MyKeyGenerator

#mybatis配置
mybatis:
  mapper-locations: "classpath*:/mapper/*.xml"

通過以上配置後, 對於兩個數據庫(ds0和ds1)的四張表(user_0和user_1)中, 分配到的user_id的最後一位分別是 :

數據表 被分配的最後一位
ds0.user_0 0, 2, 4
ds0.user_1 6, 8
ds1.user_0 5, 7, 9
ds1.user_1 1, 3

3.4 啟動類

@SpringBootApplication
@MapperScan("com.lmc.subtab.mapper")
public class ShardingJdbcApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class);
    }
}

3.5 MVC類

3.5.1 模型層

@Data
@NoArgsConstructor
@ToString
public class User {
    private Long userId;
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

3.5.2 Mapper層

@Mapper
public interface UserMapper {

    Integer insert(User user);

    User selectByUserId(Long userId);

    User selectByUsername(String username);

    List<User> selectAll();
}

配置文件 :


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lmc.subtab.mapper.UserMapper">

	<select id="selectAll" resultType="com.lmc.subtab.model.User">
		SELECT user_id userId, username, password FROM user
	select>

	<select id="selectByUserId" parameterType="long" resultType="com.lmc.subtab.model.User">
		SELECT user_id userId, username, password FROM user where user_id = #{userId}
	select>

	<select id="selectByUsername" parameterType="String" resultType="com.lmc.subtab.model.User">
		SELECT user_id userId, username, password FROM user where username = #{username}
	select>

	<insert id="insert" parameterType="com.lmc.subtab.model.User" useGeneratedKeys="true" keyProperty="userId">
		INSERT INTO user(USERNAME, PASSWORD) VALUES(#{username}, #{password})
	insert>

mapper> 

3.5.3 Service

//接口
public interface UserService {

    Integer add(User user);
    
    User selectByUserId(long userId);

    User selectByUsername(String username);

    List<User> selectAll();
}

//實現類
@Service
public class TUserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public Integer add(User user) {
        return userMapper.insert(user);
    }

    @Override
    public User selectByUserId(long userId) {
        return userMapper.selectByUserId(userId);
    }

    @Override
    public User selectByUsername(String username) {
        return userMapper.selectByUsername(username);
    }

    @Override
    public List<User> selectAll() {
        return userMapper.selectAll();
    }
}

3.5.4 Controller

@RestController
@RequestMapping("user")
public class TUserController {

    @Autowired
    private UserService userService;

    @RequestMapping("add/{name}")
    public Object add(@PathVariable String name) {
        String password = "123456";
        name = (name == null || "".equals(name)) ? "DEFAULT" : name;
        User user = new User(name, password);
        return userService.add(user);
    }

    @RequestMapping("id/{userId}")
    public User getById(@PathVariable long userId) {
        if (userId != 0) {
            return userService.selectByUserId(userId);
        }
        return null;
    }

    @RequestMapping("username/{username}")
    public User getByName(@PathVariable String username) {
        if (username != null || "".equals(username)) {
            return userService.selectByUsername(username);
        }
        return null;
    }

    @RequestMapping("list")
    public List<User> list() {
        List<User> users = userService.selectAll();
        return users;
    }

}

3.6 配置類

3.6.1 自定義主鍵生成器

定義自定義主鍵生成器,然後到配置文件裡配置

@Component
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Number generateKey() {
        return System.currentTimeMillis();
    }
}

修改yml

key-generator-class-name: com.lmc.subtab.utils.MyKeyGenerator

3.6.2 數據庫自增主鍵生成器

package com.lmc.subtab.utils;

import com.lmc.subtab.config.JdbcConfig;
import com.lmc.subtab.model.Id;
import io.shardingsphere.core.keygen.KeyGenerator;
import org.springframework.stereotype.Component;

import java.sql.*;

/**
 * @ClassName: MySqlKeyGenerator
 * @author: Leemon
 * @Description: TODO
 * @date: 2020/12/30 15:54
 * @version: 1.0
 */
@Component
public class MySqlKeyGenerator implements KeyGenerator {
    
    private static String url = "jdbc:mysql://localhost:3306/ds0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT";
    private static String username = "root";
    private static String passsword = "123456";
    private static Connection conn = null;
    private String sql = "insert into user_generator_key() values()";

    @Override
    public Number generateKey() {
        try {
            Connection connection = MySqlKeyGenerator.getConn();
            PreparedStatement stat = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            stat.executeUpdate();
            ResultSet keys = stat.getGeneratedKeys();
            int key = 0;
            while (keys.next()) {
                key = keys.getInt(1);
                System.out.println("key = " + key);
            }
            return key;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            return null;
        }
    }
    
    public static Connection getConn() {
        if (conn == null) {
            try {
                conn = DriverManager.getConnection(url, username, passsword);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return conn;
    }
    
}

修改yml

key-generator-class-name: com.lmc.subtab.utils.MySqlKeyGenerator

3.7 測試

通過訪問 http://xx.xx.xx.xx:9060/user/add/[新增用戶名] 來向user表中添加數據

通過訪問 http://xx.xx.xx.xx:9060/user/list 獲取user的所有數據

你可能感兴趣的:(数据库,分布式)