理论参考:{{MySQL 高级框架实战理论笔记}}

GitHub 代码:lane-mysql-42

项目创建与初步实现

创建父项目 mysql-example 只是为了管理依赖

创建 module sharding-jdbc-example

SQL 语句

分别创建两个 db 为 lane1 和 lane2 ,分别执行下面的 sql

  1. CREATE TABLE `position` (
  2. `Id` bigint(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(256) DEFAULT NULL,
  4. `salary` varchar(50) DEFAULT NULL,
  5. `city` varchar(256) DEFAULT NULL,
  6. PRIMARY KEY (`Id`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  8. CREATE TABLE `position_detail` (
  9. `Id` bigint(11) NOT NULL AUTO_INCREMENT,
  10. `pid` bigint(11) NOT NULL DEFAULT '0',
  11. `description` text DEFAULT NULL,
  12. PRIMARY KEY (`Id`)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

引入依赖

父模块

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.galaxy</groupId>
  7. <artifactId>mysql-example</artifactId>
  8. <packaging>pom</packaging>
  9. <version>1.0-SNAPSHOT</version>
  10. <modules>
  11. <module>sharding-jdbc-example</module>
  12. </modules>
  13. <properties>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. <project.compile.sourceEncoding>UTF-8</project.compile.sourceEncoding>
  16. <shardingsphere.version>4.1.0</shardingsphere.version>
  17. <springboot.version>2.2.9.RELEASE</springboot.version>
  18. </properties>
  19. <dependencyManagement>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-jdbc</artifactId>
  24. <version>${springboot.version}</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-data-jpa</artifactId>
  29. <version>${springboot.version}</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-starter-data-jpa</artifactId>
  34. <version>2.1.6.RELEASE</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-test</artifactId>
  39. <version>${springboot.version}</version>
  40. <scope>test</scope>
  41. </dependency>
  42. <dependency>
  43. <groupId>mysql</groupId>
  44. <artifactId>mysql-connector-java</artifactId>
  45. <version>5.1.48</version>
  46. </dependency>
  47. <dependency>
  48. <groupId>org.apache.shardingsphere</groupId>
  49. <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  50. <version>${shardingsphere.version}</version>
  51. </dependency>
  52. </dependencies>
  53. </dependencyManagement>
  54. <build>
  55. <plugins>
  56. <plugin>
  57. <groupId>org.apache.maven.plugins</groupId>
  58. <artifactId>maven-compiler-plugin</artifactId>
  59. <version>3.8.1</version>
  60. <configuration>
  61. <source>11</source>
  62. <target>11</target>
  63. <testSource>11</testSource>
  64. <testTarget>11</testTarget>
  65. </configuration>
  66. </plugin>
  67. </plugins>
  68. </build>
  69. </project>

子模块

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>mysql-example</artifactId>
  7. <groupId>com.galaxy</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>sharding-jdbc-example</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-jdbc</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-data-jpa</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-test</artifactId>
  24. <scope>test</scope>
  25. </dependency>
  26. <dependency>
  27. <groupId>mysql</groupId>
  28. <artifactId>mysql-connector-java</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.apache.shardingsphere</groupId>
  32. <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  33. </dependency>
  34. </dependencies>
  35. </project>

配置文件

application.properties

  1. spring.profiles.active=sharding-database
  2. spring.shardingsphere.props.sql.show=true

application-sharding-database.properties

  1. #datasource
  2. spring.shardingsphere.datasource.names=ds0,ds1
  3. spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
  4. spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
  5. spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8
  6. spring.shardingsphere.datasource.ds0.username=root
  7. spring.shardingsphere.datasource.ds0.password=root
  8. spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
  9. spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
  10. spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8
  11. spring.shardingsphere.datasource.ds1.username=root
  12. spring.shardingsphere.datasource.ds1.password=root
  13. #sharding-database
  14. #分片策略inline 分片键id 表达式id/2=ds0、ds1
  15. spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=id
  16. spring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{id % 2}
  17. #id 生成策略
  18. spring.shardingsphere.sharding.tables.position.key-generator.column=id
  19. spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE

Java 代码

entity

  1. package com.galaxy.entity;
  2. import javax.persistence.*;
  3. import java.io.Serializable;
  4. /**
  5. * @author lane
  6. * @date 2021年07月20日 下午7:12
  7. */
  8. @Entity
  9. @Table(name="position")
  10. public class Position implements Serializable {
  11. @Id
  12. @Column(name = "id")
  13. //使用sharding jdbc指定主键生成,不指定则是数据库默认auto_increment
  14. @GeneratedValue(strategy = GenerationType.IDENTITY)
  15. private long id;
  16. @Column(name = "name")
  17. private String name;
  18. @Column(name = "salary")
  19. private String salary;
  20. @Column(name = "city")
  21. private String city;
  22. public long getId() {
  23. return id;
  24. }
  25. public void setId(long id) {
  26. this.id = id;
  27. }
  28. public String getName() {
  29. return name;
  30. }
  31. public void setName(String name) {
  32. this.name = name;
  33. }
  34. public String getSalary() {
  35. return salary;
  36. }
  37. public void setSalary(String salary) {
  38. this.salary = salary;
  39. }
  40. public String getCity() {
  41. return city;
  42. }
  43. public void setCity(String city) {
  44. this.city = city;
  45. }
  46. }

repository

  1. package com.galaxy.repository;
  2. import com.galaxy.entity.Position;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.data.jpa.repository.Query;
  5. import org.springframework.data.repository.query.Param;
  6. /**
  7. * @author lane
  8. * @date 2021年07月20日 下午7:36
  9. */
  10. public interface PositionRepository extends JpaRepository<Position,Long> {
  11. }

启动类

  1. package com.galaxy;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. /**
  5. * @author lane
  6. * @date 2021年07月20日 下午7:31
  7. */
  8. @SpringBootApplication
  9. public class RunBootApplication {
  10. /*public static void main(String[] args) {
  11. SpringApplication.run(RunBootApplication.class,args);
  12. }*/
  13. }

测试类

  1. package dao;
  2. import com.galaxy.RunBootApplication;
  3. import com.galaxy.entity.Position;
  4. import com.galaxy.repository.PositionRepository;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import org.springframework.test.context.junit4.SpringRunner;
  10. /**
  11. * @author lane
  12. * @date 2021年07月20日 下午7:31
  13. */
  14. @RunWith(SpringRunner.class)
  15. @SpringBootTest(classes = RunBootApplication.class)
  16. public class TestShardingDatabase {
  17. @Autowired
  18. private PositionRepository positionRepository;
  19. @Test
  20. public void test0(){
  21. for (int i=1;i<20;i++){
  22. Position position =new Position();
  23. // position.setId(i);
  24. position.setCity("beijing");
  25. position.setName("zhangsan"+i);
  26. position.setSalary("20000");
  27. positionRepository.save(position);
  28. }
  29. }
  30. }

测试结果

Sharding JDBC 实战 - 图1

自定义主键生成策略

添加 Java 代码实现类

  1. package com.galaxy.id;
  2. import org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator;
  3. import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;
  4. import java.util.Properties;
  5. /**
  6. * 仍然选择雪花片算法
  7. * @author lane
  8. * @date 2021年07月21日 上午10:41
  9. */
  10. public class MyLaneId implements ShardingKeyGenerator {
  11. private SnowflakeShardingKeyGenerator snow = new SnowflakeShardingKeyGenerator();
  12. @Override
  13. public Comparable<?> generateKey() {
  14. System.out.println("------执行了自定义主键生成器-------");
  15. return snow.generateKey();
  16. }
  17. @Override
  18. public String getType() {
  19. return "LANEKEY";
  20. }
  21. @Override
  22. public Properties getProperties() {
  23. return null;
  24. }
  25. @Override
  26. public void setProperties(Properties properties) {
  27. }
  28. }

添加实现的 spi 内容

在 resources 下 新建 META-INF 文件夹及其子文件夹 service,之后添加文件名称为 org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator

内容为 com.galaxy.id.MyLaneId 的文件

Sharding JDBC 实战 - 图2

添加配置信息

  1. #id
  2. spring.shardingsphere.sharding.tables.position.key-generator.column=id
  3. #spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE
  4. spring.shardingsphere.sharding.tables.position.key-generator.type=LANEKEY

执行结果

Sharding JDBC 实战 - 图3

对应绑定表同库生成

对于 position 表和 position_detail 表有绑定关系,我们希望的是 db1 生成的 position 和 positionDetail 实现一一对应关系而不是出现 db1 的 position 和 db2 的 positionDetail 出现对应关系,这样不利于我们进行关联查询,下面是具体实现同库关联表一一绑定生成

Java 代码

positionDetail

  1. package com.galaxy.entity;
  2. import javax.persistence.*;
  3. import java.io.Serializable;
  4. /**
  5. * @author lane
  6. * @date 2021年07月21日 上午11:12
  7. */
  8. @Entity
  9. @Table(name = "position_detail")
  10. public class PositionDetail implements Serializable {
  11. @Id
  12. @Column(name = "id")
  13. @GeneratedValue(strategy = GenerationType.IDENTITY)
  14. private long id;
  15. @Column(name = "pid")
  16. private long pid;
  17. @Column(name = "description")
  18. private String description;
  19. public long getId() {
  20. return id;
  21. }
  22. public void setId(long id) {
  23. this.id = id;
  24. }
  25. public long getPid() {
  26. return pid;
  27. }
  28. public void setPid(long pid) {
  29. this.pid = pid;
  30. }
  31. public String getDescription() {
  32. return description;
  33. }
  34. public void setDescription(String description) {
  35. this.description = description;
  36. }
  37. }

repository

  1. package com.galaxy.repository;
  2. import com.galaxy.entity.PositionDetail;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. /**
  5. * @author lane
  6. * @date 2021年07月21日 上午11:13
  7. */
  8. public interface PositionDetailRepository extends JpaRepository<PositionDetail,Long> {
  9. }
  1. package com.galaxy.repository;
  2. import com.galaxy.entity.Position;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.data.jpa.repository.Query;
  5. import org.springframework.data.repository.query.Param;
  6. /**
  7. * @author lane
  8. * @date 2021年07月20日 下午7:36
  9. */
  10. public interface PositionRepository extends JpaRepository<Position,Long> {
  11. @Query(nativeQuery = true,value = "" +
  12. "select p.id,p.name,p.salary,p.city,pd.description from position p join position_detail pd on(p.id=pd.pid) where p.id=:id")
  13. public Object findPositionsById(@Param("id") long id);
  14. }

配置信息

  1. #sharding-database
  2. #分片策略inline 分片键id 表达式id/2=ds0、ds1
  3. spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=id
  4. spring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{id % 2}
  5. spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=id
  6. spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=ds$->{id % 2}
  7. #id
  8. spring.shardingsphere.sharding.tables.position.key-generator.column=id
  9. spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE
  10. #spring.shardingsphere.sharding.tables.position.key-generator.type=LANEKEY
  11. spring.shardingsphere.sharding.tables.position_detail.key-generator.column=id
  12. spring.shardingsphere.sharding.tables.position_detail.key-generator.type=SNOWFLAKE

测试代码

  1. @Test
  2. public void testAdd2(){
  3. for (int i=1;i<=20;i++){
  4. Position position = new Position();
  5. position.setName("lisi"+i);
  6. position.setSalary("1000000");
  7. position.setCity("shanghai");
  8. positionRepository.save(position);
  9. PositionDetail positionDetail = new PositionDetail();
  10. positionDetail.setPid(position.getId());
  11. positionDetail.setDescription("this is a message "+i);
  12. positionDetailRepository.save(positionDetail);
  13. }
  14. }
  15. @Test
  16. public void testLoad(){
  17. Object object = positionRepository.findPositionsById(624565543113850880L);
  18. Object[] position = (Object[])object;
  19. System.out.println(position[0]+" "+position[1]+" "+position[2]+" "+position[3]+" "+position[4]);
  20. }

测试结果

Sharding JDBC 实战 - 图4

Sharding JDBC 实战 - 图5

广播表实现

广播表是要求每个库里面的表结构和数据完全一样,一般是配置信息或者固定信息,每次操作都对每个库中的表做同样的操作

SQL 建表语句

  1. CREATE TABLE `city` (
  2. `Id` bigint(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(256) DEFAULT NULL,
  4. `province` varchar(256) DEFAULT NULL,
  5. PRIMARY KEY (`Id`)
  6. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Java 代码

city

  1. package com.galaxy.entity;
  2. import javax.persistence.*;
  3. import java.io.Serializable;
  4. /**
  5. * @author lane
  6. * @date 2021年07月21日 上午11:37
  7. */
  8. @Entity
  9. @Table(name = "city")
  10. public class City implements Serializable {
  11. @Id
  12. @Column(name = "id")
  13. @GeneratedValue(strategy = GenerationType.IDENTITY)
  14. private long id;
  15. @Column(name = "name")
  16. private String name;
  17. @Column(name = "province")
  18. private String province;
  19. public long getId() {
  20. return id;
  21. }
  22. public void setId(long id) {
  23. this.id = id;
  24. }
  25. public String getName() {
  26. return name;
  27. }
  28. public void setName(String name) {
  29. this.name = name;
  30. }
  31. public String getProvince() {
  32. return province;
  33. }
  34. public void setProvince(String province) {
  35. this.province = province;
  36. }
  37. }

repository

  1. package com.galaxy.repository;
  2. import com.galaxy.entity.City;
  3. import com.galaxy.entity.PositionDetail;
  4. import org.springframework.data.jpa.repository.JpaRepository;
  5. /**
  6. * @author lane
  7. * @date 2021年07月21日 上午11:13
  8. */
  9. public interface CityRepository extends JpaRepository<City,Long> {
  10. }

test

  1. @Test
  2. public void testBroadCast(){
  3. City city = new City();
  4. city.setName("beijing");
  5. city.setProvince("beijing");
  6. cityRepository.save(city);
  7. }

配置信息

  1. #BoardCast
  2. spring.shardingsphere.sharding.broadcast-tables=city
  3. spring.shardingsphere.sharding.tables.city.key-generator.column=id
  4. spring.shardingsphere.sharding.tables.city.key-generator.type=SNOWFLAKE

执行结果

Sharding JDBC 实战 - 图6

分库分表实现

对于公司来说会有一个订单表 BOrder,将其以公司 ID 进行分库,以 order ID 进行分表实现数据均匀存储

SQL 创建

在库 lane1 创建表 b_order0 和表 b_order1

在库 lane2 创建表 b_order0 和表 b_order1

  1. CREATE TABLE `b_order`(
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `is_del` bit(1) NOT NULL DEFAULT 0 COMMENT '是否被删除',
  4. `company_id` int(11) NOT NULL COMMENT '公司ID',
  5. `position_id` bigint(11) NOT NULL COMMENT '职位ID',
  6. `user_id` int(11) NOT NULL COMMENT '用户id',
  7. `publish_user_id` int(11) NOT NULL COMMENT '职位发布者id',
  8. `resume_type` int(2) NOT NULL DEFAULT 0 COMMENT '简历类型:0 附件 1 在线',
  9. `status` varchar(256) NOT NULL COMMENT '投递状态 投递状态 WAIT-待处理 AUTO_FILTER-自动过滤 PREPARE_CONTACT-待沟通 REFUSE-拒绝 ARRANGE_INTERVIEW-通知面试',
  10. `create_time` datetime NOT NULL COMMENT '创建时间',
  11. `operate_time` datetime NOT NULL COMMENT '操作时间',
  12. `work_year` varchar(100) DEFAULT NULL COMMENT '工作年限',
  13. `name` varchar(256) DEFAULT NULL COMMENT '投递简历人名字',
  14. `position_name` varchar(256) DEFAULT NULL COMMENT '职位名称',
  15. `resume_id` int(10) DEFAULT NULL COMMENT '投递的简历id(在线和附件都记录,通过resumeType进行区别在线还是附件)',
  16. PRIMARY KEY (`id`),
  17. KEY `index_createTime` (`create_time`),
  18. KEY `index_companyId_status` (`company_id`, `status`(255), `is_del`),
  19. KEY `i_comId_pub_ctime` (`company_id`, `publish_user_id`, `create_time`),
  20. KEY `index_companyId_positionId` (`company_id`, `position_id`) USING BTREE
  21. ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

Java 实现

BOrder

  1. package com.galaxy.entity;
  2. import javax.persistence.*;
  3. import java.io.Serializable;
  4. import java.util.Date;
  5. /**
  6. * @author lane
  7. * @date 2021年07月21日 下午12:12
  8. */
  9. @Entity
  10. //注意是逻辑表名
  11. @Table(name = "b_order")
  12. public class BOrder implements Serializable {
  13. @Id
  14. @Column(name = "id")
  15. @GeneratedValue(strategy = GenerationType.IDENTITY)
  16. private long id;
  17. @Column(name = "is_del")
  18. private Boolean isDel;
  19. @Column(name = "company_id")
  20. private Integer companyId;
  21. @Column(name = "position_id")
  22. private long positionId;
  23. @Column(name = "user_id")
  24. private Integer userId;
  25. @Column(name = "publish_user_id")
  26. private Integer publishUserId;
  27. @Column(name = "resume_type")
  28. private Integer resumeType;
  29. @Column(name = "status")
  30. private String status;
  31. @Column(name = "create_time")
  32. private Date createTime;
  33. @Column(name = "operate_time")
  34. private Date operateTime;
  35. @Column(name = "work_year")
  36. private String workYear;
  37. @Column(name = "name")
  38. private String name;
  39. @Column(name = "position_name")
  40. private String positionName;
  41. @Column(name = "resume_id")
  42. private Integer resumeId;
  43. public long getId() {
  44. return id;
  45. }
  46. public void setId(long id) {
  47. this.id = id;
  48. }
  49. public Boolean getDel() {
  50. return isDel;
  51. }
  52. public void setDel(Boolean del) {
  53. isDel = del;
  54. }
  55. public Integer getCompanyId() {
  56. return companyId;
  57. }
  58. public void setCompanyId(Integer companyId) {
  59. this.companyId = companyId;
  60. }
  61. public long getPositionId() {
  62. return positionId;
  63. }
  64. public void setPositionId(long positionId) {
  65. this.positionId = positionId;
  66. }
  67. public Integer getUserId() {
  68. return userId;
  69. }
  70. public void setUserId(Integer userId) {
  71. this.userId = userId;
  72. }
  73. public Integer getPublishUserId() {
  74. return publishUserId;
  75. }
  76. public void setPublishUserId(Integer publishUserId) {
  77. this.publishUserId = publishUserId;
  78. }
  79. public Integer getResumeType() {
  80. return resumeType;
  81. }
  82. public void setResumeType(Integer resumeType) {
  83. this.resumeType = resumeType;
  84. }
  85. public String getStatus() {
  86. return status;
  87. }
  88. public void setStatus(String status) {
  89. this.status = status;
  90. }
  91. public Date getCreateTime() {
  92. return createTime;
  93. }
  94. public void setCreateTime(Date createTime) {
  95. this.createTime = createTime;
  96. }
  97. public Date getOperateTime() {
  98. return operateTime;
  99. }
  100. public void setOperateTime(Date operateTime) {
  101. this.operateTime = operateTime;
  102. }
  103. public String getWorkYear() {
  104. return workYear;
  105. }
  106. public void setWorkYear(String workYear) {
  107. this.workYear = workYear;
  108. }
  109. public String getName() {
  110. return name;
  111. }
  112. public void setName(String name) {
  113. this.name = name;
  114. }
  115. public String getPositionName() {
  116. return positionName;
  117. }
  118. public void setPositionName(String positionName) {
  119. this.positionName = positionName;
  120. }
  121. public Integer getResumeId() {
  122. return resumeId;
  123. }
  124. public void setResumeId(Integer resumeId) {
  125. this.resumeId = resumeId;
  126. }
  127. }

BOrderRepository

  1. package com.galaxy.repository;
  2. import com.galaxy.entity.BOrder;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. /**
  5. * @author lane
  6. * @date 2021年07月21日 下午12:12
  7. */
  8. public interface BOrderRepository extends JpaRepository<BOrder,Long> {
  9. }

配置信息

  1. #sharding-database-table
  2. #spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.sharding-column=company_id
  3. #spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.algorithm-expression=ds$->{company_id % 2}
  4. #spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.sharding-column=id
  5. #spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.algorithm-expression=b_order${id % 2}
  6. #spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds${0..1}.b_order${0..1}
  7. #spring.shardingsphere.sharding.tables.b_order.key-generator.column=id
  8. #spring.shardingsphere.sharding.tables.b_order.key-generator.type=SNOWFLAKE

测试代码

  1. @Autowired
  2. BOrderRepository orderRepository;
  3. @Test
  4. @Repeat(100)
  5. public void testShardingBOrder(){
  6. Random random = new Random();
  7. int companyId = random.nextInt(100);
  8. BOrder order = new BOrder();
  9. order.setDel(false);
  10. order.setCompanyId(companyId);
  11. order.setPositionId(3242342);
  12. order.setUserId(2222);
  13. order.setPublishUserId(1111);
  14. order.setResumeType(1);
  15. order.setStatus("AUTO");
  16. order.setCreateTime(new Date());
  17. order.setOperateTime(new Date());
  18. order.setWorkYear("2");
  19. order.setName("lg");
  20. order.setPositionName("Java");
  21. order.setResumeId(23233);
  22. orderRepository.save(order);
  23. }

测试结果

不同数据库按照公司 id 进行分库,可以看到 lane1(ds0)库的公司 ID 全是 偶数,lane2(ds1)库的公司 ID 全是奇数

Sharding JDBC 实战 - 图7

对于同一个库拆分的不同表数据则是按照 ID 进行分片,可以看到下图中 b_order0 的 ID 全是偶数,而 b_order1 的 ID 全是奇数

Sharding JDBC 实战 - 图8

读写分离实现

正常读写分离一般是一台主库负责数据的写入,两台从库负责数据的读取,足以满足多数情况下了

当然了 shardingJDBC 无法实现数据的同步,仍要自己去配置数据库的同步,可以参考文章{{MySQL 安装及主从异步、半同步、同步实现}}

这次按照 city 表进行操作。lane1 库作为主库写入数据,lane2 库作为从库实现数据的读取

配置信息

application.properties

  1. spring.profiles.active=master-slave
  2. spring.shardingsphere.props.sql.show=true

application-master-slave.properties

  1. #datasource
  2. spring.shardingsphere.datasource.names=master,slave0
  3. spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource
  4. spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
  5. spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/lane1
  6. spring.shardingsphere.datasource.master.username=root
  7. spring.shardingsphere.datasource.master.password=root
  8. spring.shardingsphere.datasource.slave0.type=com.zaxxer.hikari.HikariDataSource
  9. spring.shardingsphere.datasource.slave0.driver-class-name=com.mysql.jdbc.Driver
  10. spring.shardingsphere.datasource.slave0.jdbc-url=jdbc:mysql://localhost:3306/lane2
  11. spring.shardingsphere.datasource.slave0.username=root
  12. spring.shardingsphere.datasource.slave0.password=root
  13. #master-slave
  14. spring.shardingsphere.masterslave.name=datasource
  15. spring.shardingsphere.masterslave.master-data-source-name=master
  16. spring.shardingsphere.masterslave.slave-data-source-names=slave0
  17. spring.shardingsphere.masterslave.load-balance-algorithm-type=ROUND_ROBIN
  18. spring.shardingsphere.sharding.tables.city.key-generator.column=id
  19. spring.shardingsphere.sharding.tables.city.key-generator.type=SNOWFLAKE

测试代码

  1. package dao;
  2. import com.galaxy.RunBootApplication;
  3. import com.galaxy.entity.City;
  4. import com.galaxy.repository.CityRepository;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import org.springframework.test.context.junit4.SpringRunner;
  10. import java.util.List;
  11. /**
  12. * @author lane
  13. * @date 2021年07月21日 下午3:35
  14. */
  15. @RunWith(SpringRunner.class)
  16. @SpringBootTest(classes = RunBootApplication.class)
  17. public class TestMasterSlave {
  18. @Autowired
  19. private CityRepository cityRepository;
  20. @Test
  21. public void testAdd(){
  22. City city = new City();
  23. city.setName("shanghai1");
  24. city.setProvince("shanghai1");
  25. cityRepository.save(city);
  26. }
  27. @Test
  28. public void testFind(){
  29. List<City> list = cityRepository.findAll();
  30. list.forEach(city->{
  31. System.out.println(city.getId()+" "+city.getName()+" "+city.getProvince());
  32. });
  33. }
  34. }

测试结果

执行添加之后,只在主库添加了一条数据

Sharding JDBC 实战 - 图9

执行查询则是只在从库查询到了一条数据,成功实现了读写分离

Sharding JDBC 实战 - 图10

代码指定路由 Hint 实现

仍然采用 city 表来实现,在代码中指定路由到具体某一个数据库

Java 代码

  1. package com.galaxy.hint;
  2. import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;
  3. import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;
  4. import java.util.ArrayList;
  5. import java.util.Collection;
  6. /**
  7. * @author lane
  8. * @date 2021年07月21日 下午4:50
  9. */
  10. public class MyHintShardingAlgorithm implements HintShardingAlgorithm<Long> {
  11. @Override
  12. public Collection<String> doSharding(
  13. Collection<String> availableTargetNames,
  14. HintShardingValue<Long> shardingValue) {
  15. Collection<String> result = new ArrayList<>();
  16. //each是数据源ds0和ds1
  17. for (String each : availableTargetNames){
  18. for (Long value : shardingValue.getValues()){
  19. //value是自己传入的值,根据传入的值来匹配数据源
  20. if(each.endsWith(String.valueOf(value % 2))){
  21. result.add(each);
  22. }
  23. }
  24. }
  25. return result;
  26. }
  27. }

配置文件

application.properties

  1. spring.profiles.active=hint-database
  2. spring.shardingsphere.props.sql.show=true

application-hint-database.properties

  1. #datasource
  2. spring.shardingsphere.datasource.names=ds0,ds1
  3. spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
  4. spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
  5. spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8
  6. spring.shardingsphere.datasource.ds0.username=root
  7. spring.shardingsphere.datasource.ds0.password=root
  8. spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
  9. spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
  10. spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8
  11. spring.shardingsphere.datasource.ds1.username=root
  12. spring.shardingsphere.datasource.ds1.password=root
  13. #hint
  14. spring.shardingsphere.sharding.tables.city.database-strategy.hint.algorithm-class-name=com.galaxy.hint.MyHintShardingAlgorithm

测试代码

  1. package dao;
  2. import com.galaxy.RunBootApplication;
  3. import com.galaxy.entity.City;
  4. import com.galaxy.repository.CityRepository;
  5. import org.apache.shardingsphere.api.hint.HintManager;
  6. import org.junit.Test;
  7. import org.junit.runner.RunWith;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import org.springframework.test.context.junit4.SpringRunner;
  10. import javax.annotation.Resource;
  11. import java.util.List;
  12. /**
  13. * @author lane
  14. * @date 2021年07月21日 下午4:52
  15. */
  16. @RunWith(SpringRunner.class)
  17. @SpringBootTest(classes = RunBootApplication.class)
  18. public class TestHintAlgorithm {
  19. @Resource
  20. private CityRepository cityRepository;
  21. @Test
  22. public void test1(){
  23. HintManager hintManager = HintManager.getInstance();
  24. //强制路由到ds${xx%2}既是 ds2%2 = ds0
  25. hintManager.setDatabaseShardingValue(2L);
  26. List<City> list = cityRepository.findAll();
  27. list.forEach(city->{
  28. System.out.println(city.getId()+" "+city.getName()+" "+city.getProvince());
  29. });
  30. }
  31. }

测试结果

查询的是 ds0 也就是 lane1 库里的 City 数据两条

Sharding JDBC 实战 - 图11

数据脱敏(加解密)实现

通过根据逻辑列在数据库中添加明文列(非必须)和密文列(必须)实现数据加密存储和数据解密查询效果

新建用户表实现密码的脱敏

创建 SQL

  1. CREATE TABLE `c_user` (
  2. `Id` bigint(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(256) DEFAULT NULL,
  4. `pwd_plain` varchar(256) DEFAULT NULL,
  5. `pwd_cipher` varchar(256) DEFAULT NULL,
  6. PRIMARY KEY (`Id`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Java 代码实现

CUser

  1. package com.galaxy.entity;
  2. import javax.persistence.*;
  3. import java.io.Serializable;
  4. /**
  5. * @author lane
  6. * @date 2021年07月21日 下午6:33
  7. */
  8. @Entity
  9. @Table(name = "c_user")
  10. public class CUser implements Serializable {
  11. @Id
  12. @Column(name = "id")
  13. @GeneratedValue(strategy = GenerationType.IDENTITY)
  14. private long id;
  15. @Column(name = "name")
  16. private String name;
  17. @Column(name = "pwd")//逻辑列名
  18. private String pwd;
  19. public long getId() {
  20. return id;
  21. }
  22. public void setId(long id) {
  23. this.id = id;
  24. }
  25. public String getName() {
  26. return name;
  27. }
  28. public void setName(String name) {
  29. this.name = name;
  30. }
  31. public String getPwd() {
  32. return pwd;
  33. }
  34. public void setPwd(String pwd) {
  35. this.pwd = pwd;
  36. }
  37. }

Repository

  1. package com.galaxy.repository;
  2. import com.galaxy.entity.CUser;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import java.util.List;
  5. /**
  6. * @author lane
  7. * @date 2021年07月21日 下午6:35
  8. */
  9. public interface CUserRepository extends JpaRepository<CUser,Long> {
  10. List<CUser> findByPwd(String pwd);
  11. }

配置文件

application.properties

  1. spring.profiles.active=encryptor
  2. spring.shardingsphere.props.sql.show=true

application-encryptor.properties

  1. #datasource
  2. spring.shardingsphere.datasource.names=ds0,ds1
  3. spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
  4. spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
  5. spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8
  6. spring.shardingsphere.datasource.ds0.username=root
  7. spring.shardingsphere.datasource.ds0.password=root
  8. spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
  9. spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
  10. spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8
  11. spring.shardingsphere.datasource.ds1.username=root
  12. spring.shardingsphere.datasource.ds1.password=root
  13. #sharding-database
  14. #分片策略inline 分片键id 表达式id/2=ds0、ds1
  15. spring.shardingsphere.sharding.tables.c_user.database-strategy.inline.sharding-column=id
  16. spring.shardingsphere.sharding.tables.c_user.database-strategy.inline.algorithm-expression=ds$->{id % 2}
  17. #id
  18. spring.shardingsphere.sharding.tables.c_user.key-generator.column=id
  19. spring.shardingsphere.sharding.tables.c_user.key-generator.type=SNOWFLAKE
  20. #encrypt
  21. spring.shardingsphere.encrypt.tables.c_user.columns.pwd.plain-column=pwd_plain
  22. spring.shardingsphere.encrypt.tables.c_user.columns.pwd.cipher-column=pwd_cipher
  23. spring.shardingsphere.encrypt.encryptors.la_pwd.type=aes
  24. spring.shardingsphere.encrypt.encryptors.la_pwd.props.aes.key.value=1234
  25. spring.shardingsphere.encrypt.tables.c_user.columns.pwd.encryptor=la_pwd
  26. #是否使用密文查询 默认true,false是指明文查询
  27. spring.shardingsphere.props.query.with.cipher.column=true

测试类

  1. package dao;
  2. import com.galaxy.RunBootApplication;
  3. import com.galaxy.entity.CUser;
  4. import com.galaxy.repository.CUserRepository;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import org.springframework.test.annotation.Repeat;
  10. import org.springframework.test.context.junit4.SpringRunner;
  11. import java.util.List;
  12. import java.util.Random;
  13. /**
  14. * @author lane
  15. * @date 2021年07月21日 下午6:41
  16. */
  17. @SpringBootTest(classes = RunBootApplication.class)
  18. @RunWith(SpringRunner.class)
  19. public class TestEncryptor {
  20. @Autowired
  21. private CUserRepository userRepository;
  22. @Test
  23. @Repeat(20)
  24. public void testAdd(){
  25. CUser user = new CUser();
  26. int i = new Random().nextInt(20);
  27. user.setName("tiger"+ i);
  28. user.setPwd("abc"+i);
  29. userRepository.save(user);
  30. }
  31. @Test
  32. public void testFind(){
  33. List<CUser> list = userRepository.findByPwd("abc");
  34. list.forEach(cUser -> {
  35. System.out.println(cUser.getId()+" "+cUser.getName()+" "+cUser.getPwd());
  36. });
  37. }
  38. }

测试结果

可以看到存储两列分别是明文密码和密文密码

不过在分库雪花片算法生成主键这方面有问题,依然按照默认的数据库 auto_increment 的生成策略

Sharding JDBC 实战 - 图12

执行查询语句

Sharding JDBC 实战 - 图13

实现分布式事务 XA

XA 是强一致性,Saga 是最终一致性,2PC,3PC 都是分布式事务规范,需要具体实现如 Seata

本地实现按照 lane1 position 和 lane2 的 position_detail 来实现

依赖添加

父 pom.xml

  1. <dependency>
  2. <groupId>org.apache.shardingsphere</groupId>
  3. <artifactId>sharding-transaction-xa-core</artifactId>
  4. <version>${shardingsphere.version}</version>
  5. </dependency>

子 pom.xml 添加

  1. <dependency>
  2. <groupId>org.apache.shardingsphere</groupId>
  3. <artifactId>sharding-transaction-xa-core</artifactId>
  4. </dependency>

配置修改

application.properties

  1. spring.profiles.active=sharding-database
  2. spring.shardingsphere.props.sql.show=true

application-sharding-database.properties

注意这次指定了分别为 lane1 和 lane2 并没有按 id 分片

  1. #datasource
  2. spring.shardingsphere.datasource.names=ds0,ds1
  3. spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
  4. spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
  5. spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8
  6. spring.shardingsphere.datasource.ds0.username=root
  7. spring.shardingsphere.datasource.ds0.password=root
  8. spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
  9. spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
  10. spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8
  11. spring.shardingsphere.datasource.ds1.username=root
  12. spring.shardingsphere.datasource.ds1.password=root
  13. #sharding-database
  14. #分片策略inline 分片键id 表达式id/2=ds0、ds1
  15. spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=id
  16. spring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{0}
  17. spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=id
  18. spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=ds$->{1}
  19. #id
  20. spring.shardingsphere.sharding.tables.position.key-generator.column=id
  21. spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE
  22. #spring.shardingsphere.sharding.tables.position.key-generator.type=LANEKEY
  23. spring.shardingsphere.sharding.tables.position_detail.key-generator.column=id
  24. spring.shardingsphere.sharding.tables.position_detail.key-generator.type=SNOWFLAKE
  25. #BoardCast
  26. #spring.shardingsphere.sharding.broadcast-tables=city
  27. #spring.shardingsphere.sharding.tables.city.key-generator.column=id
  28. #spring.shardingsphere.sharding.tables.city.key-generator.type=SNOWFLAKE
  29. #sharding-database-table
  30. #spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.sharding-column=company_id
  31. #spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.algorithm-expression=ds$->{company_id % 2}
  32. #spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.sharding-column=id
  33. #spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.algorithm-expression=b_order${id % 2}
  34. #spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds${0..1}.b_order${0..1}
  35. #spring.shardingsphere.sharding.tables.b_order.key-generator.column=id
  36. #spring.shardingsphere.sharding.tables.b_order.key-generator.type=SNOWFLAKE

代码实现

启动类

  1. package com.galaxy;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.transaction.annotation.EnableTransactionManagement;
  5. /**
  6. * @author lane
  7. * @date 2021年07月20日 下午7:31
  8. */
  9. @SpringBootApplication
  10. @EnableTransactionManagement
  11. public class RunBootApplication {
  12. /*public static void main(String[] args) {
  13. SpringApplication.run(RunBootApplication.class,args);
  14. }*/
  15. }

测试类

  1. /**
  2. * @author lane
  3. * @date 2021年07月22日 下午4:43
  4. */
  5. @RunWith(SpringRunner.class)
  6. @SpringBootTest(classes = RunBootApplication.class)
  7. public class TestShardingTransaction {
  8. @Resource
  9. private PositionRepository positionRepository;
  10. @Resource
  11. private PositionDetailRepository positionDetailRepository;
  12. @Test
  13. // @Transactional
  14. // @ShardingTransactionType(TransactionType.XA)
  15. public void test1(){
  16. // TransactionTypeHolder.set(TransactionType.XA);
  17. // for (int i=1;i<=5;i++){
  18. int i=1;
  19. Position position = new Position();
  20. position.setName("root"+i);
  21. position.setSalary("1000000");
  22. position.setCity("beijing");
  23. positionRepository.save(position);
  24. if (i==2){
  25. throw new RuntimeException("人为制造异常");
  26. }
  27. PositionDetail positionDetail = new PositionDetail();
  28. positionDetail.setPid(position.getId());
  29. positionDetail.setDescription("this is a root "+i);
  30. positionDetailRepository.save(positionDetail);
  31. }
  32. }

正常情况的结果,两个不同的库都添加了一条数据

Sharding JDBC 实战 - 图14

修改测试代码 i=2 后如下,此时会出现异常,一个库的 position 执行成功,另一个库 position_detail 执行失败

  1. /**
  2. * @author lane
  3. * @date 2021年07月22日 下午4:43
  4. */
  5. @RunWith(SpringRunner.class)
  6. @SpringBootTest(classes = RunBootApplication.class)
  7. public class TestShardingTransaction {
  8. @Resource
  9. private PositionRepository positionRepository;
  10. @Resource
  11. private PositionDetailRepository positionDetailRepository;
  12. @Test
  13. @Transactional
  14. //@ShardingTransactionType(TransactionType.XA)
  15. public void test1(){
  16. // TransactionTypeHolder.set(TransactionType.XA);
  17. // for (int i=1;i<=5;i++){
  18. int i=2;
  19. Position position = new Position();
  20. position.setName("root"+i);
  21. position.setSalary("1000000");
  22. position.setCity("beijing");
  23. positionRepository.save(position);
  24. if (i==2){
  25. throw new RuntimeException("人为制造异常");
  26. }
  27. PositionDetail positionDetail = new PositionDetail();
  28. positionDetail.setPid(position.getId());
  29. positionDetail.setDescription("this is a root "+i);
  30. positionDetailRepository.save(positionDetail);
  31. }
  32. }

添加异常后的结果

Sharding JDBC 实战 - 图15

删除刚才的数据,再次修改添加如下代码

  1. TransactionTypeHolder.set(TransactionType.XA);

再次测试,可以看到成功回滚了

Sharding JDBC 实战 - 图16

Sharding JDBC 实战 - 图17

实际上搞得我有点尴尬 😅,因为理论上来说,添加注解也可以实现异常回滚的,然而事实上并没有,只有在代码中添加才成功实现了异常回滚。