SpringBoot使用sharding-jdbc分庫分表

閱讀目錄

回到頂部

一、前言

  一般來說,隨着業務的發展數據庫的數據量會越來越多,當單表數據超過上千萬時執行一些查詢sql語句就會遇到性能問題。一開始可以用主從複製讀寫分離來減輕db壓力,但是後面還是要用分庫分表把數據進行水平拆分和垂直拆分。
  實現分庫分表目前我知道的方式有兩種,第一種是使用mycat中間件實現,第二種是使用sharding-jdbc實現。相比較而言,sharding-jdbc引入一個jar包即可使用更輕量級一些,它們之間的優缺點這裏也不做比較,有興趣的可以自己搜索相關資料。
  不清楚分庫分表原理的可以參考這篇博客,數據庫之分庫分表-垂直?水平?

回到頂部

二、使用噹噹網的sharding-jdbc分庫分表

2.1新建SpringBoot項目

新建項目sharding-jdbc-first,並在pom文件添加如下內容:

  1. <parent> 
  2. <groupId>org.springframework.boot</groupId> 
  3. <artifactId>spring-boot-starter-parent</artifactId> 
  4. <version>1.5.16.RELEASE</version> 
  5. <relativePath/> <!-- lookup parent from repository --> 
  6. </parent> 
  7.  
  8. <properties> 
  9. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
  10. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 
  11. <java.version>1.8</java.version> 
  12. </properties> 
  13.  
  14. <dependencies> 
  15. <dependency> 
  16. <groupId>org.springframework.boot</groupId> 
  17. <artifactId>spring-boot-starter-data-jpa</artifactId> 
  18. </dependency> 
  19. <dependency> 
  20. <groupId>org.springframework.boot</groupId> 
  21. <artifactId>spring-boot-starter-web</artifactId> 
  22. </dependency> 
  23.  
  24. <dependency> 
  25. <groupId>mysql</groupId> 
  26. <artifactId>mysql-connector-java</artifactId> 
  27. <scope>runtime</scope> 
  28. </dependency> 
  29. <dependency> 
  30. <groupId>org.springframework.boot</groupId> 
  31. <artifactId>spring-boot-starter-test</artifactId> 
  32. <scope>test</scope> 
  33. </dependency> 
  34. <dependency> 
  35. <groupId>com.dangdang</groupId> 
  36. <artifactId>sharding-jdbc-core</artifactId> 
  37. <version>1.4.2</version> 
  38. </dependency> 
  39. <dependency> 
  40. <groupId>com.alibaba</groupId> 
  41. <artifactId>druid</artifactId> 
  42. <version>1.0.12</version> 
  43. </dependency> 
  44.  
  45. <dependency> 
  46. <groupId>com.dangdang</groupId> 
  47. <artifactId>sharding-jdbc-self-id-generator</artifactId> 
  48. <version>1.4.2</version> 
  49. </dependency> 
  50.  
  51.  
  52. </dependencies> 

目前好像不支持SpringBoot2.0以上的版本。

2.2編寫實體類及建庫建表

目標:
db0
├── t_order_0 user_id爲偶數 order_id爲偶數
├── t_order_1 user_id爲偶數 order_id爲奇數
db1
├── t_order_0 user_id爲奇數 order_id爲偶數
├── t_order_1 user_id爲奇數 order_id爲奇數


  1. 創建兩個數據庫 ds_0 和 ds_1,編碼類型UTF-8。
  2. 每個庫分表創建兩個表t_order_0和t_order_1,sql語句如下:

    DROP TABLE IF EXISTS t_order_0;
    CREATE TABLE t_order_0 (
    order_id bigint(20) NOT NULL,
    user_id bigint(20) NOT NULL,
    PRIMARY KEY (order_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
  3. 新建類Order,代碼如下
  1. package cn.sp.bean; 
  2.  
  3. import javax.persistence.Entity; 
  4. import javax.persistence.Id; 
  5. import javax.persistence.Table; 
  6.  
  7. /** 
  8. * Created by 2YSP on 2018/9/23. 
  9. */ 
  10. @Entity 
  11. @Table(name="t_order") 
  12. public class Order { 
  13. @Id 
  14. private Long orderId; 
  15.  
  16. private Long userId; 
  17.  
  18. public Long getOrderId() { 
  19. return orderId; 

  20.  
  21. public void setOrderId(Long orderId) { 
  22. this.orderId = orderId; 

  23.  
  24. public Long getUserId() { 
  25. return userId; 

  26.  
  27. public void setUserId(Long userId) { 
  28. this.userId = userId; 


  29.  

這裏需要注意 @Id註解不要導錯包,之前我就遇到過這個問題。
4.配置文件application.yml

  1. server: 
  2. port: 8000 
  3. spring: 
  4. jpa: 
  5. database: mysql 
  6. show-sql: true 
  7. hibernate: 
  8. ## 自己建表 
  9. ddl-auto: none 
  10. application: 
  11. name: sharding-jdbc-first 

這裏要注意的是spring-data-jpa默認會自己建表,這裏我們要手動建立,所以需要將ddl-auto屬性設置爲none。

2.3自定義分庫分表算法

1.分庫算法類需要實現SingleKeyDatabaseShardingAlgorithm<T>接口,這是一個泛型接口,T代表分庫依據的字段的類型,比如我們根據userId%2來分庫,userId是Long型的,這裏的T就是Long。

  1. public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> { 
  2. @Override 
  3. public String doEqualSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) { 
  4. for(String databaseName : availableDatabaseNames){ 
  5. if (databaseName.endsWith(shardingValue.getValue() % 2 + "")){ 
  6.  
  7. return databaseName; 


  8. throw new IllegalArgumentException(); 

  9.  
  10. @Override 
  11. public Collection<String> doInSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) { 
  12. Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size()); 
  13. for(Long value : shardingValue.getValues()){ 
  14. for(String name : availableDatabaseNames){ 
  15. if (name.endsWith(value%2 + "")){ 
  16. result.add(name); 



  17. return result; 

  18.  
  19. @Override 
  20. public Collection<String> doBetweenSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) { 
  21. Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size()); 
  22. Range<Long> range = shardingValue.getValueRange(); 
  23. for(Long i = range.lowerEndpoint() ; i < range.upperEndpoint();i++){ 
  24. for(String each : availableDatabaseNames){ 
  25. if (each.endsWith( i % 2+"")){ 
  26. result.add(each); 



  27.  
  28. return result; 


  29.  

2.分表算法類需要實現SingleKeyTableShardingAlgorithm<T>接口。

  1. /** 
  2. * 表分片算法 
  3. * Created by 2YSP on 2018/9/23. 
  4. */ 
  5. public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> { 
  6.  
  7. /** 
  8. * select * from t_order from t_order where order_id = 11 
  9. * └── SELECT * FROM t_order_1 WHERE order_id = 11 
  10. * select * from t_order from t_order where order_id = 44 
  11. * └── SELECT * FROM t_order_0 WHERE order_id = 44 
  12. */ 
  13. @Override 
  14. public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { 
  15. for (String tableName : tableNames) { 
  16. if (tableName.endsWith(shardingValue.getValue() % 2 + "")) { 
  17. return tableName; 


  18.  
  19. throw new IllegalArgumentException(); 

  20.  
  21. /** 
  22. * select * from t_order from t_order where order_id in (11,44) 
  23. * ├── SELECT * FROM t_order_0 WHERE order_id IN (11,44) 
  24. * └── SELECT * FROM t_order_1 WHERE order_id IN (11,44) 
  25. * select * from t_order from t_order where order_id in (11,13,15) 
  26. * └── SELECT * FROM t_order_1 WHERE order_id IN (11,13,15) 
  27. * select * from t_order from t_order where order_id in (22,24,26) 
  28. * └──SELECT * FROM t_order_0 WHERE order_id IN (22,24,26) 
  29. */ 
  30. @Override 
  31. public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { 
  32. Collection<String> result = new LinkedHashSet<>(tableNames.size()); 
  33. for (Long value : shardingValue.getValues()) { 
  34. for (String table : tableNames) { 
  35. if (table.endsWith(value % 2 + "")) { 
  36. result.add(table); 



  37. return result; 

  38.  
  39. /** 
  40. * select * from t_order from t_order where order_id between 10 and 20 
  41. * ├── SELECT * FROM t_order_0 WHERE order_id BETWEEN 10 AND 20 
  42. * └── SELECT * FROM t_order_1 WHERE order_id BETWEEN 10 AND 20 
  43. */ 
  44. @Override 
  45. public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { 
  46. Collection<String> result = new LinkedHashSet<>(tableNames.size()); 
  47. Range<Long> range = shardingValue.getValueRange(); 
  48. for (Long i = range.lowerEndpoint(); i < range.upperEndpoint(); i++) { 
  49. for (String each : tableNames) { 
  50. if (each.endsWith(i % 2 + "")) { 
  51. result.add(each); 



  52.  
  53. return result; 


2.4配置數據源

數據源配置類DataSourceConfig

  1. @Configuration 
  2. public class DataSourceConfig { 
  3. @Bean 
  4. public IdGenerator getIdGenerator(){ 
  5. return new CommonSelfIdGenerator(); 

  6.  
  7. @Bean 
  8. public DataSource getDataSource() { 
  9. return buildDataSource(); 

  10.  
  11.  
  12. private DataSource buildDataSource() { 
  13. //1.設置分庫映射 
  14. Map<String, DataSource> dataSourceMap = new HashMap<>(2); 
  15. dataSourceMap.put("ds_0", createDataSource("ds_0")); 
  16. dataSourceMap.put("ds_1", createDataSource("ds_1")); 
  17. //設置默認db爲ds_0,也就是爲那些沒有配置分庫分表策略的指定的默認庫 
  18. //如果只有一個庫,也就是不需要分庫的話,map裏只放一個映射就行了,只有一個庫時不需要指定默認庫, 
  19. // 但2個及以上時必須指定默認庫,否則那些沒有配置策略的表將無法操作數據 
  20. DataSourceRule rule = new DataSourceRule(dataSourceMap, "ds_0"); 
  21.  
  22. //2.設置分表映射,將t_order_0和t_order_1兩個實際的表映射到t_order邏輯表 
  23. TableRule orderTableRule = TableRule.builder("t_order") 
  24. .actualTables(Arrays.asList("t_order_0", "t_order_1")) 
  25. .dataSourceRule(rule) 
  26. .build(); 
  27. //3.具體的分庫分表策略 
  28. ShardingRule shardingRule = ShardingRule.builder() 
  29. .dataSourceRule(rule) 
  30. .tableRules(Arrays.asList(orderTableRule)) 
  31. .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm())) 
  32. .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())) 
  33. .build(); 
  34.  
  35. DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule); 
  36. return dataSource; 

  37.  
  38. private static DataSource createDataSource(String dataSourceName) { 
  39. //使用druid連接數據庫 
  40. DruidDataSource druidDataSource = new DruidDataSource(); 
  41. druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); 
  42. druidDataSource.setUrl(String.format("jdbc:mysql://localhost:3306/%s?characterEncoding=utf-8", dataSourceName)); 
  43. druidDataSource.setUsername("root"); 
  44. druidDataSource.setPassword("1234"); 
  45. return druidDataSource; 


這裏的一些配置信息url,username,password等可以優化下,從配置文件讀取。

2.5測試

1.新建OrderRepository

  1. public interface OrderRepository extends CrudRepository<Order,Long> { 
  2.  

2.controller層

  1. /** 
  2. * Created by 2YSP on 2018/9/23. 
  3. */ 
  4. @RestController 
  5. @RequestMapping("/order") 
  6. public class OrderController { 
  7.  
  8. @Autowired 
  9. private OrderRepository repository; 
  10.  
  11. @Autowired 
  12. private IdGenerator idGenerator; 
  13.  
  14. @RequestMapping("/add") 
  15. public String add(){ 
  16. for(int i=0;i<10;i++){ 
  17. Order order = new Order(); 
  18. order.setOrderId((long) i); 
  19. order.setUserId((long) i); 
  20. repository.save(order); 

  21. // Order order = new Order(); 
  22. // order.setUserId(1L); 
  23. // order.setOrderId(idGenerator.generateId().longValue()); 
  24. // repository.save(order); 
  25. return "success"; 

  26.  
  27. @RequestMapping("/query") 
  28. public List<Order> queryAll(){ 
  29. List<Order> orders = (List<Order>) repository.findAll(); 
  30. return orders; 


  31.  

3.訪問http://localhost:8080/order/add,即可在數據庫ds_0,ds_1發現多了一些數據。
訪問http://localhost:8080/order/query可以查詢剛剛添加的訂單數據。
完整代碼地址:https://github.com/2YSP/sharding-jdbc-first

回到頂部

三、使用sharding-jdbc-spring-boot-starter分庫分表

3.1引入依賴

因爲我的SpringBoot是2.X版本,所以引入最新的依賴。因爲目前的maven倉庫(包括阿里倉庫)還沒有對應的jar,需要自己去github下載源代碼,然後執行 mvn clean install打包到本地maven倉庫。

  1. <dependency> 
  2. <groupId>io.shardingsphere</groupId> 
  3. <artifactId>sharding-jdbc-spring-boot-starter</artifactId> 
  4. <version>3.0.0.M4</version> 
  5. </dependency> 

3.2SpringBoot配置

在application.properties文件添加如下內容:

##########分庫分表配置##########
sharding.jdbc.datasource.names=ds0,ds1
## 這裏使用阿里的Druid連接池
sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds0.url=jdbc:mysql://localhost:3306/ds_0
sharding.jdbc.datasource.ds0.username=root
sharding.jdbc.datasource.ds0.password=1234

sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds1.url=jdbc:mysql://localhost:3306/ds_1
sharding.jdbc.datasource.ds1.username=root
sharding.jdbc.datasource.ds1.password=1234

##默認的分庫策略:user_id爲奇數-->數據庫ds_1,user_id爲偶數-->數據庫ds_0
sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=user_id
sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
## 這裏的t_order是邏輯表,由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}
## 行表達式分片策略
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 2}

這裏還可以用Java配置,Yaml配置來代替,感興趣的話可以訪問github地址瞭解更多,上面有對應的中文文檔。

回到頂部

四、總結

在分庫分表的時候要根據實際情況來決定根據哪個字段來分(不一定都是主鍵),需要分幾個庫幾張表。
分庫分表後遇到的問題:
1.不能像以前一樣使用數據庫自增的主鍵了,會出現主鍵重複的問題(可以使用分佈式主鍵來代替)。
2.不支持一些關鍵字。
3.在做一些統計查詢的時候也更加困難,那時候可能需要引入搜索引擎ES了。
4.之前以爲sharding-jdbc不支持分頁操作,那天測試了下竟然可以。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章