[转载]--- 开箱即用的springboot分库分表sharding-JDBC

SpringBoot使用Sharding-JDBC分库分表

1.有关Sharding-JDBC

有关Sharding-JDBC介绍这里就不在多说,之前Sharding-JDBC是当当网自研的关系型数据库的水平扩展框架,现在已经捐献给Apache,具体可以查看Github,地址是:https://shardingsphere.apache.org/document/current/cn/overview/

shardingsphere文档地址是:https://shardingsphere.apache.org/document/current/cn/overview/

目前貌似还不能从Maven仓库下载依赖,需要手动下载源码打包使用,所以本文使用的还是当当网的依赖。

2.本文场景

2.1 数据库

接下来介绍一下本文的场景,本文是分别创建了2个数据库database0和database1。其中每个数据库都创建了2个数据表,goods_0和goods_1,如图所示。这里蓝色的代表database0中的表,红色的代表database1中的表。绿色goods表是虚拟表(图画的比较丑,审美不好,凑合看吧)。

2.2 分库

本文分库样例比较简单,根据数据库表中字段goods_id的大小进行判断,如果goods_id大于20则使用database0,否则使用database1。

2.3 分表

分样例比较简单,根据数据库表中字段goods_type的数值的奇偶进行判断,奇数使用goods_1表,偶数使用goods_0表。

2.4 代码流程

流程大致是这样,在应用程序中我们操作虚拟表goods,但是当真正操作数据库的时候,会根据我们的分库分表规则进行匹配然后操作。

3.代码实现

本文使用SpringBoot2.0.3,SpringData-JPA,Druid连接池,和当当的sharding-jdbc。

3.1 建表SQL

创建表和数据库的SQL如下所示。


 
  1. CREATE DATABASE database0;
  2. USE database0;
  3. DROP TABLE IF EXISTS `goods_0`;
  4. CREATE TABLE `goods_0` (
  5. `goods_id` bigint( 20) NOT NULL,
  6. `goods_name` varchar( 100) COLLATE utf8_bin NOT NULL,
  7. `goods_type` bigint( 20) DEFAULT NULL,
  8. PRIMARY KEY ( `goods_id`)
  9. ) ENGINE= InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
  10. DROP TABLE IF EXISTS `goods_1`;
  11. CREATE TABLE `goods_1` (
  12. `goods_id` bigint( 20) NOT NULL,
  13. `goods_name` varchar( 100) COLLATE utf8_bin NOT NULL,
  14. `goods_type` bigint( 20) DEFAULT NULL,
  15. PRIMARY KEY ( `goods_id`)
  16. ) ENGINE= InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
  17. CREATE DATABASE database1;
  18. USE database1;
  19. DROP TABLE IF EXISTS `goods_0`;
  20. CREATE TABLE `goods_0` (
  21. `goods_id` bigint( 20) NOT NULL,
  22. `goods_name` varchar( 100) COLLATE utf8_bin NOT NULL,
  23. `goods_type` bigint( 20) DEFAULT NULL,
  24. PRIMARY KEY ( `goods_id`)
  25. ) ENGINE= InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
  26. DROP TABLE IF EXISTS `goods_1`;
  27. CREATE TABLE `goods_1` (
  28. `goods_id` bigint( 20) NOT NULL,
  29. `goods_name` varchar( 100) COLLATE utf8_bin NOT NULL,
  30. `goods_type` bigint( 20) DEFAULT NULL,
  31. PRIMARY KEY ( `goods_id`)
  32. ) ENGINE= InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

3.2 依赖文件

新建项目,加入当当的sharding-jdbc-core依赖和druid连接池,完整pom如下所示。


 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0 </modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot </groupId>
  7. <artifactId>spring-boot-starter-parent </artifactId>
  8. <version>2.0.3.RELEASE </version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.dalaoyang </groupId>
  12. <artifactId>springboot2_shardingjdbc_fkfb </artifactId>
  13. <version>0.0.1-SNAPSHOT </version>
  14. <name>springboot2_shardingjdbc_fkfb </name>
  15. <description>springboot2_shardingjdbc_fkfb </description>
  16. <properties>
  17. <java.version>1.8 </java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot </groupId>
  22. <artifactId>spring-boot-starter-data-jpa </artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot </groupId>
  26. <artifactId>spring-boot-starter-web </artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot </groupId>
  30. <artifactId>spring-boot-devtools </artifactId>
  31. <scope>runtime </scope>
  32. </dependency>
  33. <dependency>
  34. <groupId>mysql </groupId>
  35. <artifactId>mysql-connector-java </artifactId>
  36. <scope>runtime </scope>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.springframework.boot </groupId>
  40. <artifactId>spring-boot-starter-test </artifactId>
  41. <scope>test </scope>
  42. </dependency>
  43. <!-- lombok -->
  44. <dependency>
  45. <groupId>org.projectlombok </groupId>
  46. <artifactId>lombok </artifactId>
  47. <optional>true </optional>
  48. </dependency>
  49. <!-- druid -->
  50. <dependency>
  51. <groupId>com.alibaba </groupId>
  52. <artifactId>druid </artifactId>
  53. <version>1.1.9 </version>
  54. </dependency>
  55. <!-- sharding-jdbc -->
  56. <dependency>
  57. <groupId>com.dangdang </groupId>
  58. <artifactId>sharding-jdbc-core </artifactId>
  59. <version>1.5.4 </version>
  60. </dependency>
  61. </dependencies>
  62. <build>
  63. <plugins>
  64. <plugin>
  65. <groupId>org.springframework.boot </groupId>
  66. <artifactId>spring-boot-maven-plugin </artifactId>
  67. </plugin>
  68. </plugins>
  69. </build>
  70. </project>

3.3 配置信息

在配置信息中配置了两个数据库的信息和JPA的简单配置。


 
  1. ##Jpa配置
  2. spring.jpa.database=mysql
  3. spring.jpa.show-sql= true
  4. spring.jpa.hibernate.ddl-auto=none
  5. ##数据库配置
  6. ##数据库database0地址
  7. database 0.url= jdbc: mysql:/ /localhost:3306/database 0?characterEncoding=utf8&useSSL= false
  8. ##数据库database0用户名
  9. database 0.username=root
  10. ##数据库database0密码
  11. database 0.password=root
  12. ##数据库database0驱动
  13. database 0.driverClassName=com.mysql.jdbc.Driver
  14. ##数据库database0名称
  15. database 0.databaseName=database 0
  16. ##数据库database1地址
  17. database1.url= jdbc: mysql:/ /localhost:3306/database1?characterEncoding=utf8&useSSL= false
  18. ##数据库database1用户名
  19. database1.username=root
  20. ##数据库database1密码
  21. database1.password=root
  22. ##数据库database1驱动
  23. database1.driverClassName=com.mysql.jdbc.Driver
  24. ##数据库database1名称
  25. database1.databaseName=database1

3.4 启动类

启动类加入了@EnableAutoConfiguration取出数据库自动配置,使用@EnableTransactionManagement开启事务,使用@EnableConfigurationProperties注解加入配置实体,启动类完整代码请入所示。


 
  1. package com.dalaoyang;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  6. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  7. import org.springframework.context.annotation.ComponentScan;
  8. import org.springframework.transaction.annotation.EnableTransactionManagement;
  9. @ SpringBootApplication
  10. @ EnableAutoConfiguration(exclude={ DataSourceAutoConfiguration. class})
  11. @ EnableTransactionManagement(proxyTargetClass = true)
  12. @ EnableConfigurationProperties
  13. public class Springboot2ShardingjdbcFkfbApplication {
  14. public static void main( String[] args) {
  15. SpringApplication.run( Springboot2ShardingjdbcFkfbApplication. class, args);
  16. }
  17. }

3.5 实体类和数据库操作层

这里没什么好说的,就是简单的实体和Repository,只不过在Repository内加入between方法和in方法用于测试,代码如下所示。

Goods实体类。


 
  1. package com.dalaoyang.entity;
  2. import lombok.Data;
  3. import javax.persistence.Entity;
  4. import javax.persistence.Id;
  5. import javax.persistence.Table;
  6. /**
  7. * @author yangyang
  8. * @date 2019/1/29
  9. */
  10. @Entity
  11. @Table(name="goods")
  12. @Data
  13. public class Goods {
  14. @Id
  15. private Long goodsId;
  16. private String goodsName;
  17. private Long goodsType;
  18. }

GoodsRepository类。


 
  1. package com.dalaoyang.repository;
  2. import com.dalaoyang.entity.Goods;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import java.util.List;
  5. /**
  6. * @author yangyang
  7. * @date 2019/1/29
  8. */
  9. public interface GoodsRepository extends JpaRepository<Goods, Long> {
  10. List<Goods> findAllByGoodsIdBetween(Long goodsId1,Long goodsId2);
  11. List<Goods> findAllByGoodsIdIn(List<Long> goodsIds);
  12. }

3.6 数据库配置

本文使用了两个实体来接收数据库信息,并且创建数据源,也可以采用别的方式。首先看一下Database0Config和Database1Config两个类的代码。

Database0Config类。


 
  1. package com.dalaoyang.database;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import lombok.Data;
  4. import org.springframework.boot.context.properties.ConfigurationProperties;
  5. import org.springframework.stereotype.Component;
  6. import javax.sql.DataSource;
  7. /**
  8. * @author yangyang
  9. * @date 2019/1/30
  10. */
  11. @Data
  12. @ConfigurationProperties(prefix = "database0")
  13. @Component
  14. public class Database0Config {
  15. private String url;
  16. private String username;
  17. private String password;
  18. private String driverClassName;
  19. private String databaseName;
  20. public DataSource createDataSource() {
  21. DruidDataSource result = new DruidDataSource();
  22. result.setDriverClassName(getDriverClassName());
  23. result.setUrl(getUrl());
  24. result.setUsername(getUsername());
  25. result.setPassword(getPassword());
  26. return result;
  27. }
  28. }

Database1Config类。


 
  1. package com.dalaoyang.database;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import lombok.Data;
  4. import org.springframework.boot.context.properties.ConfigurationProperties;
  5. import org.springframework.stereotype.Component;
  6. import javax.sql.DataSource;
  7. /**
  8. * @author yangyang
  9. * @date 2019/1/30
  10. */
  11. @Data
  12. @ConfigurationProperties(prefix = "database1")
  13. @Component
  14. public class Database1Config {
  15. private String url;
  16. private String username;
  17. private String password;
  18. private String driverClassName;
  19. private String databaseName;
  20. public DataSource createDataSource() {
  21. DruidDataSource result = new DruidDataSource();
  22. result.setDriverClassName(getDriverClassName());
  23. result.setUrl(getUrl());
  24. result.setUsername(getUsername());
  25. result.setPassword(getPassword());
  26. return result;
  27. }
  28. }

接下来新建DataSourceConfig用于创建数据源和使用分库分表策略,其中分库分表策略会调用分库算法类和分表算法类,DataSourceConfig类代码如下所示。


 
  1. package com.dalaoyang.database;
  2. import com.dalaoyang.config.DatabaseShardingAlgorithm;
  3. import com.dalaoyang.config.TableShardingAlgorithm;
  4. import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSourceFactory;
  5. import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule;
  6. import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule;
  7. import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule;
  8. import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy;
  9. import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy;
  10. import com.dangdang.ddframe.rdb.sharding.keygen.DefaultKeyGenerator;
  11. import com.dangdang.ddframe.rdb.sharding.keygen.KeyGenerator;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.context.annotation.Bean;
  14. import org.springframework.context.annotation.Configuration;
  15. import javax.sql.DataSource;
  16. import java.sql.SQLException;
  17. import java.util.Arrays;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. /**
  21. * @author yangyang
  22. * @date 2019/1/29
  23. */
  24. @ Configuration
  25. public class DataSourceConfig {
  26. @ Autowired
  27. private Database0Config database0Config;
  28. @ Autowired
  29. private Database1Config database1Config;
  30. @ Autowired
  31. private DatabaseShardingAlgorithm databaseShardingAlgorithm;
  32. @ Autowired
  33. private TableShardingAlgorithm tableShardingAlgorithm;
  34. @ Bean
  35. public DataSource getDataSource() throws SQLException {
  36. return buildDataSource();
  37. }
  38. private DataSource buildDataSource() throws SQLException {
  39. //分库设置
  40. Map< String, DataSource> dataSourceMap = new HashMap<>( 2);
  41. //添加两个数据库database0和database1
  42. dataSourceMap.put(database0Config.getDatabaseName(), database0Config.createDataSource());
  43. dataSourceMap.put(database1Config.getDatabaseName(), database1Config.createDataSource());
  44. //设置默认数据库
  45. DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, database0Config.getDatabaseName());
  46. //分表设置,大致思想就是将查询虚拟表Goods根据一定规则映射到真实表中去
  47. TableRule orderTableRule = TableRule.builder( "goods")
  48. .actualTables( Arrays.asList( "goods_0", "goods_1"))
  49. .dataSourceRule(dataSourceRule)
  50. .build();
  51. //分库分表策略
  52. ShardingRule shardingRule = ShardingRule.builder()
  53. .dataSourceRule(dataSourceRule)
  54. .tableRules( Arrays.asList(orderTableRule))
  55. .databaseShardingStrategy(new DatabaseShardingStrategy( "goods_id", databaseShardingAlgorithm))
  56. .tableShardingStrategy(new TableShardingStrategy( "goods_type", tableShardingAlgorithm)).build();
  57. DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
  58. return dataSource;
  59. }
  60. @ Bean
  61. public KeyGenerator keyGenerator() {
  62. return new DefaultKeyGenerator();
  63. }
  64. }

3.7 分库分表算法

由于这里只是简单的分库分表样例,所以分库类这里实现SingleKeyDatabaseShardingAlgorithm类,采用了单分片键数据源分片算法,需要重写三个方法,分别是:

  • doEqualSharding:SQL中==的规则。
  • doInSharding:SQL中in的规则。
  • doBetweenSharding:SQL中between的规则。

本文分库规则是基于值大于20则使用database0,其余使用database1,所以简单if,else就搞定了,分库算法类DatabaseShardingAlgorithm代码如下所示。


 
  1. package com.dalaoyang.config;
  2. import com.dalaoyang.database.Database0Config;
  3. import com.dalaoyang.database.Database1Config;
  4. import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
  5. import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm;
  6. import com.google.common.collect.Range;
  7. import org.springframework.beans.factory. annotation.Autowired;
  8. import org.springframework.stereotype.Component;
  9. import java.util.Collection;
  10. import java.util.LinkedHashSet;
  11. /**
  12. * 这里使用的都是单键分片策略
  13. * 示例分库策略是:
  14. * GoodsId<=20使用database0库
  15. * 其余使用database1库
  16. * @author yangyang
  17. * @date 2019/1/30
  18. */
  19. @Component
  20. public class DatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {
  21. @Autowired
  22. private Database0Config database0Config;
  23. @Autowired
  24. private Database1Config database1Config;
  25. @Override
  26. public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue< Long> shardingValue) {
  27. Long value = shardingValue.getValue();
  28. if (value <= 20L) {
  29. return database0Config.getDatabaseName();
  30. } else {
  31. return database1Config.getDatabaseName();
  32. }
  33. }
  34. @Override
  35. public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue< Long> shardingValue) {
  36. Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
  37. for ( Long value : shardingValue.getValues()) {
  38. if (value <= 20L) {
  39. result.add(database0Config.getDatabaseName());
  40. } else {
  41. result.add(database1Config.getDatabaseName());
  42. }
  43. }
  44. return result;
  45. }
  46. @Override
  47. public Collection<String> doBetweenSharding(Collection<String> availableTargetNames,
  48. ShardingValue< Long> shardingValue) {
  49. Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
  50. Range< Long> range = shardingValue.getValueRange();
  51. for ( Long value = range.lowerEndpoint(); value <= range.upperEndpoint(); value++) {
  52. if (value <= 20L) {
  53. result.add(database0Config.getDatabaseName());
  54. } else {
  55. result.add(database1Config.getDatabaseName());
  56. }
  57. }
  58. return result;
  59. }
  60. }

分表和分库类似,无非就是实现的类不一样,实现了SingleKeyTableShardingAlgorithm类,策略使用值奇偶分表,分表算法类TableShardingAlgorithm如代码清单所示。


 
  1. package com.dalaoyang.config;
  2. import com.dangdang.ddframe.rdb.sharding.api.ShardingValue;
  3. import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm;
  4. import com.google.common.collect.Range;
  5. import org.springframework.stereotype.Component;
  6. import java.util.Collection;
  7. import java.util.LinkedHashSet;
  8. /**
  9. * 这里使用的都是单键分片策略
  10. * 示例分表策略是:
  11. * GoodsType为奇数使用goods_1表
  12. * GoodsType为偶数使用goods_0表
  13. * @author yangyang
  14. * @date 2019/1/30
  15. */
  16. @Component
  17. public class TableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {
  18. @Override
  19. public String doEqualSharding( final Collection<String> tableNames, final ShardingValue< Long> shardingValue) {
  20. for (String each : tableNames) {
  21. if (each.endsWith(shardingValue.getValue() % 2 + "")) {
  22. return each;
  23. }
  24. }
  25. throw new IllegalArgumentException();
  26. }
  27. @Override
  28. public Collection<String> doInSharding( final Collection<String> tableNames, final ShardingValue< Long> shardingValue) {
  29. Collection<String> result = new LinkedHashSet<>(tableNames.size());
  30. for ( Long value : shardingValue.getValues()) {
  31. for (String tableName : tableNames) {
  32. if (tableName.endsWith(value % 2 + "")) {
  33. result.add(tableName);
  34. }
  35. }
  36. }
  37. return result;
  38. }
  39. @Override
  40. public Collection<String> doBetweenSharding( final Collection<String> tableNames,
  41. final ShardingValue< Long> shardingValue) {
  42. Collection<String> result = new LinkedHashSet<>(tableNames.size());
  43. Range< Long> range = shardingValue.getValueRange();
  44. for ( Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
  45. for (String each : tableNames) {
  46. if (each.endsWith(i % 2 + "")) {
  47. result.add(each);
  48. }
  49. }
  50. }
  51. return result;
  52. }
  53. }

3.8 Controller

接下来创建一个Controller进行测试,保存方法使用了插入40条数据,根据我们的规则,会每个库插入20条,同时我这里还创建了三个查询方法,分别是查询全部,between查询,in查询,还有删除全部方法。Controller类代码如下所示。


 
  1. package com.dalaoyang.controller;
  2. import com.dalaoyang.entity.Goods;
  3. import com.dalaoyang.repository.GoodsRepository;
  4. import com.dangdang.ddframe.rdb.sharding.keygen.KeyGenerator;
  5. import org.springframework.beans.factory. annotation.Autowired;
  6. import org.springframework.web.bind. annotation.GetMapping;
  7. import org.springframework.web.bind. annotation.RestController;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. /**
  11. * @author yangyang
  12. * @date 2019/1/29
  13. */
  14. @RestController
  15. public class GoodsController {
  16. @Autowired
  17. private KeyGenerator keyGenerator;
  18. @Autowired
  19. private GoodsRepository goodsRepository;
  20. @GetMapping("save")
  21. public String save(){
  22. for(int i= 1 ; i <= 40 ; i ++){
  23. Goods goods = new Goods();
  24. goods.setGoodsId((long) i);
  25. goods.setGoodsName( "shangpin" + i);
  26. goods.setGoodsType((long) (i+ 1));
  27. goodsRepository.save(goods);
  28. }
  29. return "success";
  30. }
  31. @GetMapping("select")
  32. public String select(){
  33. return goodsRepository.findAll().toString();
  34. }
  35. @GetMapping("delete")
  36. public void delete(){
  37. goodsRepository.deleteAll();
  38. }
  39. @GetMapping("query1")
  40. public Object query1(){
  41. return goodsRepository.findAllByGoodsIdBetween( 10L, 30L);
  42. }
  43. @GetMapping("query2")
  44. public Object query2(){
  45. List< Long> goodsIds = new ArrayList<>();
  46. goodsIds.add( 10L);
  47. goodsIds.add( 15L);
  48. goodsIds.add( 20L);
  49. goodsIds.add( 25L);
  50. return goodsRepository.findAllByGoodsIdIn(goodsIds);
  51. }
  52. }

4.测试

启动应用,在浏览器或HTTP请求工具访问http://localhost:8080/save,如图所示,返回success。

接下来在测试一下查询方法,访问http://localhost:8080/select,如图所示,可以看到插入数据没问题。

然后查看一下数据库,首先看database0,如图,每个表都有十条数据,如下所示。

接下来看database1,如下所示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值