理论参考:{{MySQL 高级框架实战理论笔记}}
GitHub 代码:lane-mysql-42
项目创建与初步实现
创建父项目 mysql-example 只是为了管理依赖
创建 module sharding-jdbc-example
SQL 语句
分别创建两个 db 为 lane1 和 lane2 ,分别执行下面的 sql
CREATE TABLE `position` (`Id` bigint(11) NOT NULL AUTO_INCREMENT,`name` varchar(256) DEFAULT NULL,`salary` varchar(50) DEFAULT NULL,`city` varchar(256) DEFAULT NULL,PRIMARY KEY (`Id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `position_detail` (`Id` bigint(11) NOT NULL AUTO_INCREMENT,`pid` bigint(11) NOT NULL DEFAULT '0',`description` text DEFAULT NULL,PRIMARY KEY (`Id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
引入依赖
父模块
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.galaxy</groupId><artifactId>mysql-example</artifactId><packaging>pom</packaging><version>1.0-SNAPSHOT</version><modules><module>sharding-jdbc-example</module></modules><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.compile.sourceEncoding>UTF-8</project.compile.sourceEncoding><shardingsphere.version>4.1.0</shardingsphere.version><springboot.version>2.2.9.RELEASE</springboot.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>${springboot.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId><version>${springboot.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId><version>2.1.6.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>${springboot.version}</version><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>${shardingsphere.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>11</source><target>11</target><testSource>11</testSource><testTarget>11</testTarget></configuration></plugin></plugins></build></project>
子模块
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>mysql-example</artifactId><groupId>com.galaxy</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>sharding-jdbc-example</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId></dependency></dependencies></project>
配置文件
application.properties
spring.profiles.active=sharding-databasespring.shardingsphere.props.sql.show=true
application-sharding-database.properties
#datasourcespring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds0.username=rootspring.shardingsphere.datasource.ds0.password=rootspring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds1.username=rootspring.shardingsphere.datasource.ds1.password=root#sharding-database#分片策略inline 分片键id 表达式id/2=ds0、ds1spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=idspring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{id % 2}#id 生成策略spring.shardingsphere.sharding.tables.position.key-generator.column=idspring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE
Java 代码
entity
package com.galaxy.entity;import javax.persistence.*;import java.io.Serializable;/*** @author lane* @date 2021年07月20日 下午7:12*/@Entity@Table(name="position")public class Position implements Serializable {@Id@Column(name = "id")//使用sharding jdbc指定主键生成,不指定则是数据库默认auto_increment@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(name = "name")private String name;@Column(name = "salary")private String salary;@Column(name = "city")private String city;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSalary() {return salary;}public void setSalary(String salary) {this.salary = salary;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}}
repository
package com.galaxy.repository;import com.galaxy.entity.Position;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.query.Param;/*** @author lane* @date 2021年07月20日 下午7:36*/public interface PositionRepository extends JpaRepository<Position,Long> {}
启动类
package com.galaxy;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author lane* @date 2021年07月20日 下午7:31*/@SpringBootApplicationpublic class RunBootApplication {/*public static void main(String[] args) {SpringApplication.run(RunBootApplication.class,args);}*/}
测试类
package dao;import com.galaxy.RunBootApplication;import com.galaxy.entity.Position;import com.galaxy.repository.PositionRepository;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;/*** @author lane* @date 2021年07月20日 下午7:31*/@RunWith(SpringRunner.class)@SpringBootTest(classes = RunBootApplication.class)public class TestShardingDatabase {@Autowiredprivate PositionRepository positionRepository;@Testpublic void test0(){for (int i=1;i<20;i++){Position position =new Position();// position.setId(i);position.setCity("beijing");position.setName("zhangsan"+i);position.setSalary("20000");positionRepository.save(position);}}}
测试结果

自定义主键生成策略
添加 Java 代码实现类
package com.galaxy.id;import org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator;import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;import java.util.Properties;/*** 仍然选择雪花片算法* @author lane* @date 2021年07月21日 上午10:41*/public class MyLaneId implements ShardingKeyGenerator {private SnowflakeShardingKeyGenerator snow = new SnowflakeShardingKeyGenerator();@Overridepublic Comparable<?> generateKey() {System.out.println("------执行了自定义主键生成器-------");return snow.generateKey();}@Overridepublic String getType() {return "LANEKEY";}@Overridepublic Properties getProperties() {return null;}@Overridepublic void setProperties(Properties properties) {}}
添加实现的 spi 内容
在 resources 下 新建 META-INF 文件夹及其子文件夹 service,之后添加文件名称为 org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator
内容为 com.galaxy.id.MyLaneId 的文件

添加配置信息
#idspring.shardingsphere.sharding.tables.position.key-generator.column=id#spring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKEspring.shardingsphere.sharding.tables.position.key-generator.type=LANEKEY
执行结果

对应绑定表同库生成
对于 position 表和 position_detail 表有绑定关系,我们希望的是 db1 生成的 position 和 positionDetail 实现一一对应关系而不是出现 db1 的 position 和 db2 的 positionDetail 出现对应关系,这样不利于我们进行关联查询,下面是具体实现同库关联表一一绑定生成
Java 代码
positionDetail
package com.galaxy.entity;import javax.persistence.*;import java.io.Serializable;/*** @author lane* @date 2021年07月21日 上午11:12*/@Entity@Table(name = "position_detail")public class PositionDetail implements Serializable {@Id@Column(name = "id")@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(name = "pid")private long pid;@Column(name = "description")private String description;public long getId() {return id;}public void setId(long id) {this.id = id;}public long getPid() {return pid;}public void setPid(long pid) {this.pid = pid;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}}
repository
package com.galaxy.repository;import com.galaxy.entity.PositionDetail;import org.springframework.data.jpa.repository.JpaRepository;/*** @author lane* @date 2021年07月21日 上午11:13*/public interface PositionDetailRepository extends JpaRepository<PositionDetail,Long> {}
package com.galaxy.repository;import com.galaxy.entity.Position;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.query.Param;/*** @author lane* @date 2021年07月20日 下午7:36*/public interface PositionRepository extends JpaRepository<Position,Long> {@Query(nativeQuery = true,value = "" +"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")public Object findPositionsById(@Param("id") long id);}
配置信息
#sharding-database#分片策略inline 分片键id 表达式id/2=ds0、ds1spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=idspring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{id % 2}spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=idspring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=ds$->{id % 2}#idspring.shardingsphere.sharding.tables.position.key-generator.column=idspring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE#spring.shardingsphere.sharding.tables.position.key-generator.type=LANEKEYspring.shardingsphere.sharding.tables.position_detail.key-generator.column=idspring.shardingsphere.sharding.tables.position_detail.key-generator.type=SNOWFLAKE
测试代码
@Testpublic void testAdd2(){for (int i=1;i<=20;i++){Position position = new Position();position.setName("lisi"+i);position.setSalary("1000000");position.setCity("shanghai");positionRepository.save(position);PositionDetail positionDetail = new PositionDetail();positionDetail.setPid(position.getId());positionDetail.setDescription("this is a message "+i);positionDetailRepository.save(positionDetail);}}@Testpublic void testLoad(){Object object = positionRepository.findPositionsById(624565543113850880L);Object[] position = (Object[])object;System.out.println(position[0]+" "+position[1]+" "+position[2]+" "+position[3]+" "+position[4]);}
测试结果


广播表实现
广播表是要求每个库里面的表结构和数据完全一样,一般是配置信息或者固定信息,每次操作都对每个库中的表做同样的操作
SQL 建表语句
CREATE TABLE `city` (`Id` bigint(11) NOT NULL AUTO_INCREMENT,`name` varchar(256) DEFAULT NULL,`province` varchar(256) DEFAULT NULL,PRIMARY KEY (`Id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Java 代码
city
package com.galaxy.entity;import javax.persistence.*;import java.io.Serializable;/*** @author lane* @date 2021年07月21日 上午11:37*/@Entity@Table(name = "city")public class City implements Serializable {@Id@Column(name = "id")@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(name = "name")private String name;@Column(name = "province")private String province;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getProvince() {return province;}public void setProvince(String province) {this.province = province;}}
repository
package com.galaxy.repository;import com.galaxy.entity.City;import com.galaxy.entity.PositionDetail;import org.springframework.data.jpa.repository.JpaRepository;/*** @author lane* @date 2021年07月21日 上午11:13*/public interface CityRepository extends JpaRepository<City,Long> {}
test
@Testpublic void testBroadCast(){City city = new City();city.setName("beijing");city.setProvince("beijing");cityRepository.save(city);}
配置信息
#BoardCastspring.shardingsphere.sharding.broadcast-tables=cityspring.shardingsphere.sharding.tables.city.key-generator.column=idspring.shardingsphere.sharding.tables.city.key-generator.type=SNOWFLAKE
执行结果

分库分表实现
对于公司来说会有一个订单表 BOrder,将其以公司 ID 进行分库,以 order ID 进行分表实现数据均匀存储
SQL 创建
在库 lane1 创建表 b_order0 和表 b_order1
在库 lane2 创建表 b_order0 和表 b_order1
CREATE TABLE `b_order`(`id` bigint(20) NOT NULL AUTO_INCREMENT,`is_del` bit(1) NOT NULL DEFAULT 0 COMMENT '是否被删除',`company_id` int(11) NOT NULL COMMENT '公司ID',`position_id` bigint(11) NOT NULL COMMENT '职位ID',`user_id` int(11) NOT NULL COMMENT '用户id',`publish_user_id` int(11) NOT NULL COMMENT '职位发布者id',`resume_type` int(2) NOT NULL DEFAULT 0 COMMENT '简历类型:0 附件 1 在线',`status` varchar(256) NOT NULL COMMENT '投递状态 投递状态 WAIT-待处理 AUTO_FILTER-自动过滤 PREPARE_CONTACT-待沟通 REFUSE-拒绝 ARRANGE_INTERVIEW-通知面试',`create_time` datetime NOT NULL COMMENT '创建时间',`operate_time` datetime NOT NULL COMMENT '操作时间',`work_year` varchar(100) DEFAULT NULL COMMENT '工作年限',`name` varchar(256) DEFAULT NULL COMMENT '投递简历人名字',`position_name` varchar(256) DEFAULT NULL COMMENT '职位名称',`resume_id` int(10) DEFAULT NULL COMMENT '投递的简历id(在线和附件都记录,通过resumeType进行区别在线还是附件)',PRIMARY KEY (`id`),KEY `index_createTime` (`create_time`),KEY `index_companyId_status` (`company_id`, `status`(255), `is_del`),KEY `i_comId_pub_ctime` (`company_id`, `publish_user_id`, `create_time`),KEY `index_companyId_positionId` (`company_id`, `position_id`) USING BTREE) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
Java 实现
BOrder
package com.galaxy.entity;import javax.persistence.*;import java.io.Serializable;import java.util.Date;/*** @author lane* @date 2021年07月21日 下午12:12*/@Entity//注意是逻辑表名@Table(name = "b_order")public class BOrder implements Serializable {@Id@Column(name = "id")@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(name = "is_del")private Boolean isDel;@Column(name = "company_id")private Integer companyId;@Column(name = "position_id")private long positionId;@Column(name = "user_id")private Integer userId;@Column(name = "publish_user_id")private Integer publishUserId;@Column(name = "resume_type")private Integer resumeType;@Column(name = "status")private String status;@Column(name = "create_time")private Date createTime;@Column(name = "operate_time")private Date operateTime;@Column(name = "work_year")private String workYear;@Column(name = "name")private String name;@Column(name = "position_name")private String positionName;@Column(name = "resume_id")private Integer resumeId;public long getId() {return id;}public void setId(long id) {this.id = id;}public Boolean getDel() {return isDel;}public void setDel(Boolean del) {isDel = del;}public Integer getCompanyId() {return companyId;}public void setCompanyId(Integer companyId) {this.companyId = companyId;}public long getPositionId() {return positionId;}public void setPositionId(long positionId) {this.positionId = positionId;}public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}public Integer getPublishUserId() {return publishUserId;}public void setPublishUserId(Integer publishUserId) {this.publishUserId = publishUserId;}public Integer getResumeType() {return resumeType;}public void setResumeType(Integer resumeType) {this.resumeType = resumeType;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Date getOperateTime() {return operateTime;}public void setOperateTime(Date operateTime) {this.operateTime = operateTime;}public String getWorkYear() {return workYear;}public void setWorkYear(String workYear) {this.workYear = workYear;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPositionName() {return positionName;}public void setPositionName(String positionName) {this.positionName = positionName;}public Integer getResumeId() {return resumeId;}public void setResumeId(Integer resumeId) {this.resumeId = resumeId;}}
BOrderRepository
package com.galaxy.repository;import com.galaxy.entity.BOrder;import org.springframework.data.jpa.repository.JpaRepository;/*** @author lane* @date 2021年07月21日 下午12:12*/public interface BOrderRepository extends JpaRepository<BOrder,Long> {}
配置信息
#sharding-database-table#spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.sharding-column=company_id#spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.algorithm-expression=ds$->{company_id % 2}#spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.sharding-column=id#spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.algorithm-expression=b_order${id % 2}#spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds${0..1}.b_order${0..1}#spring.shardingsphere.sharding.tables.b_order.key-generator.column=id#spring.shardingsphere.sharding.tables.b_order.key-generator.type=SNOWFLAKE
测试代码
@AutowiredBOrderRepository orderRepository;@Test@Repeat(100)public void testShardingBOrder(){Random random = new Random();int companyId = random.nextInt(100);BOrder order = new BOrder();order.setDel(false);order.setCompanyId(companyId);order.setPositionId(3242342);order.setUserId(2222);order.setPublishUserId(1111);order.setResumeType(1);order.setStatus("AUTO");order.setCreateTime(new Date());order.setOperateTime(new Date());order.setWorkYear("2");order.setName("lg");order.setPositionName("Java");order.setResumeId(23233);orderRepository.save(order);}
测试结果
不同数据库按照公司 id 进行分库,可以看到 lane1(ds0)库的公司 ID 全是 偶数,lane2(ds1)库的公司 ID 全是奇数

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

读写分离实现
正常读写分离一般是一台主库负责数据的写入,两台从库负责数据的读取,足以满足多数情况下了
当然了 shardingJDBC 无法实现数据的同步,仍要自己去配置数据库的同步,可以参考文章{{MySQL 安装及主从异步、半同步、同步实现}}
这次按照 city 表进行操作。lane1 库作为主库写入数据,lane2 库作为从库实现数据的读取
配置信息
application.properties
spring.profiles.active=master-slavespring.shardingsphere.props.sql.show=true
application-master-slave.properties
#datasourcespring.shardingsphere.datasource.names=master,slave0spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/lane1spring.shardingsphere.datasource.master.username=rootspring.shardingsphere.datasource.master.password=rootspring.shardingsphere.datasource.slave0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.slave0.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.slave0.jdbc-url=jdbc:mysql://localhost:3306/lane2spring.shardingsphere.datasource.slave0.username=rootspring.shardingsphere.datasource.slave0.password=root#master-slavespring.shardingsphere.masterslave.name=datasourcespring.shardingsphere.masterslave.master-data-source-name=masterspring.shardingsphere.masterslave.slave-data-source-names=slave0spring.shardingsphere.masterslave.load-balance-algorithm-type=ROUND_ROBINspring.shardingsphere.sharding.tables.city.key-generator.column=idspring.shardingsphere.sharding.tables.city.key-generator.type=SNOWFLAKE
测试代码
package dao;import com.galaxy.RunBootApplication;import com.galaxy.entity.City;import com.galaxy.repository.CityRepository;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.util.List;/*** @author lane* @date 2021年07月21日 下午3:35*/@RunWith(SpringRunner.class)@SpringBootTest(classes = RunBootApplication.class)public class TestMasterSlave {@Autowiredprivate CityRepository cityRepository;@Testpublic void testAdd(){City city = new City();city.setName("shanghai1");city.setProvince("shanghai1");cityRepository.save(city);}@Testpublic void testFind(){List<City> list = cityRepository.findAll();list.forEach(city->{System.out.println(city.getId()+" "+city.getName()+" "+city.getProvince());});}}
测试结果
执行添加之后,只在主库添加了一条数据

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

代码指定路由 Hint 实现
仍然采用 city 表来实现,在代码中指定路由到具体某一个数据库
Java 代码
package com.galaxy.hint;import org.apache.shardingsphere.api.sharding.hint.HintShardingAlgorithm;import org.apache.shardingsphere.api.sharding.hint.HintShardingValue;import java.util.ArrayList;import java.util.Collection;/*** @author lane* @date 2021年07月21日 下午4:50*/public class MyHintShardingAlgorithm implements HintShardingAlgorithm<Long> {@Overridepublic Collection<String> doSharding(Collection<String> availableTargetNames,HintShardingValue<Long> shardingValue) {Collection<String> result = new ArrayList<>();//each是数据源ds0和ds1for (String each : availableTargetNames){for (Long value : shardingValue.getValues()){//value是自己传入的值,根据传入的值来匹配数据源if(each.endsWith(String.valueOf(value % 2))){result.add(each);}}}return result;}}
配置文件
application.properties
spring.profiles.active=hint-databasespring.shardingsphere.props.sql.show=true
application-hint-database.properties
#datasourcespring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds0.username=rootspring.shardingsphere.datasource.ds0.password=rootspring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds1.username=rootspring.shardingsphere.datasource.ds1.password=root#hintspring.shardingsphere.sharding.tables.city.database-strategy.hint.algorithm-class-name=com.galaxy.hint.MyHintShardingAlgorithm
测试代码
package dao;import com.galaxy.RunBootApplication;import com.galaxy.entity.City;import com.galaxy.repository.CityRepository;import org.apache.shardingsphere.api.hint.HintManager;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import javax.annotation.Resource;import java.util.List;/*** @author lane* @date 2021年07月21日 下午4:52*/@RunWith(SpringRunner.class)@SpringBootTest(classes = RunBootApplication.class)public class TestHintAlgorithm {@Resourceprivate CityRepository cityRepository;@Testpublic void test1(){HintManager hintManager = HintManager.getInstance();//强制路由到ds${xx%2}既是 ds2%2 = ds0hintManager.setDatabaseShardingValue(2L);List<City> list = cityRepository.findAll();list.forEach(city->{System.out.println(city.getId()+" "+city.getName()+" "+city.getProvince());});}}
测试结果
查询的是 ds0 也就是 lane1 库里的 City 数据两条

数据脱敏(加解密)实现
通过根据逻辑列在数据库中添加明文列(非必须)和密文列(必须)实现数据加密存储和数据解密查询效果
新建用户表实现密码的脱敏
创建 SQL
CREATE TABLE `c_user` (`Id` bigint(11) NOT NULL AUTO_INCREMENT,`name` varchar(256) DEFAULT NULL,`pwd_plain` varchar(256) DEFAULT NULL,`pwd_cipher` varchar(256) DEFAULT NULL,PRIMARY KEY (`Id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Java 代码实现
CUser
package com.galaxy.entity;import javax.persistence.*;import java.io.Serializable;/*** @author lane* @date 2021年07月21日 下午6:33*/@Entity@Table(name = "c_user")public class CUser implements Serializable {@Id@Column(name = "id")@GeneratedValue(strategy = GenerationType.IDENTITY)private long id;@Column(name = "name")private String name;@Column(name = "pwd")//逻辑列名private String pwd;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}}
Repository
package com.galaxy.repository;import com.galaxy.entity.CUser;import org.springframework.data.jpa.repository.JpaRepository;import java.util.List;/*** @author lane* @date 2021年07月21日 下午6:35*/public interface CUserRepository extends JpaRepository<CUser,Long> {List<CUser> findByPwd(String pwd);}
配置文件
application.properties
spring.profiles.active=encryptorspring.shardingsphere.props.sql.show=true
application-encryptor.properties
#datasourcespring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds0.username=rootspring.shardingsphere.datasource.ds0.password=rootspring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds1.username=rootspring.shardingsphere.datasource.ds1.password=root#sharding-database#分片策略inline 分片键id 表达式id/2=ds0、ds1spring.shardingsphere.sharding.tables.c_user.database-strategy.inline.sharding-column=idspring.shardingsphere.sharding.tables.c_user.database-strategy.inline.algorithm-expression=ds$->{id % 2}#idspring.shardingsphere.sharding.tables.c_user.key-generator.column=idspring.shardingsphere.sharding.tables.c_user.key-generator.type=SNOWFLAKE#encryptspring.shardingsphere.encrypt.tables.c_user.columns.pwd.plain-column=pwd_plainspring.shardingsphere.encrypt.tables.c_user.columns.pwd.cipher-column=pwd_cipherspring.shardingsphere.encrypt.encryptors.la_pwd.type=aesspring.shardingsphere.encrypt.encryptors.la_pwd.props.aes.key.value=1234spring.shardingsphere.encrypt.tables.c_user.columns.pwd.encryptor=la_pwd#是否使用密文查询 默认true,false是指明文查询spring.shardingsphere.props.query.with.cipher.column=true
测试类
package dao;import com.galaxy.RunBootApplication;import com.galaxy.entity.CUser;import com.galaxy.repository.CUserRepository;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.annotation.Repeat;import org.springframework.test.context.junit4.SpringRunner;import java.util.List;import java.util.Random;/*** @author lane* @date 2021年07月21日 下午6:41*/@SpringBootTest(classes = RunBootApplication.class)@RunWith(SpringRunner.class)public class TestEncryptor {@Autowiredprivate CUserRepository userRepository;@Test@Repeat(20)public void testAdd(){CUser user = new CUser();int i = new Random().nextInt(20);user.setName("tiger"+ i);user.setPwd("abc"+i);userRepository.save(user);}@Testpublic void testFind(){List<CUser> list = userRepository.findByPwd("abc");list.forEach(cUser -> {System.out.println(cUser.getId()+" "+cUser.getName()+" "+cUser.getPwd());});}}
测试结果
可以看到存储两列分别是明文密码和密文密码
不过在分库雪花片算法生成主键这方面有问题,依然按照默认的数据库 auto_increment 的生成策略

执行查询语句

实现分布式事务 XA
XA 是强一致性,Saga 是最终一致性,2PC,3PC 都是分布式事务规范,需要具体实现如 Seata
本地实现按照 lane1 position 和 lane2 的 position_detail 来实现
依赖添加
父 pom.xml
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-transaction-xa-core</artifactId><version>${shardingsphere.version}</version></dependency>
子 pom.xml 添加
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-transaction-xa-core</artifactId></dependency>
配置修改
application.properties
spring.profiles.active=sharding-databasespring.shardingsphere.props.sql.show=true
application-sharding-database.properties
注意这次指定了分别为 lane1 和 lane2 并没有按 id 分片
#datasourcespring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://localhost:3306/lane1?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds0.username=rootspring.shardingsphere.datasource.ds0.password=rootspring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/lane2?useUnicode=true&characterEncoding=utf8spring.shardingsphere.datasource.ds1.username=rootspring.shardingsphere.datasource.ds1.password=root#sharding-database#分片策略inline 分片键id 表达式id/2=ds0、ds1spring.shardingsphere.sharding.tables.position.database-strategy.inline.sharding-column=idspring.shardingsphere.sharding.tables.position.database-strategy.inline.algorithm-expression=ds$->{0}spring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.sharding-column=idspring.shardingsphere.sharding.tables.position_detail.database-strategy.inline.algorithm-expression=ds$->{1}#idspring.shardingsphere.sharding.tables.position.key-generator.column=idspring.shardingsphere.sharding.tables.position.key-generator.type=SNOWFLAKE#spring.shardingsphere.sharding.tables.position.key-generator.type=LANEKEYspring.shardingsphere.sharding.tables.position_detail.key-generator.column=idspring.shardingsphere.sharding.tables.position_detail.key-generator.type=SNOWFLAKE#BoardCast#spring.shardingsphere.sharding.broadcast-tables=city#spring.shardingsphere.sharding.tables.city.key-generator.column=id#spring.shardingsphere.sharding.tables.city.key-generator.type=SNOWFLAKE#sharding-database-table#spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.sharding-column=company_id#spring.shardingsphere.sharding.tables.b_order.database-strategy.inline.algorithm-expression=ds$->{company_id % 2}#spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.sharding-column=id#spring.shardingsphere.sharding.tables.b_order.table-strategy.inline.algorithm-expression=b_order${id % 2}#spring.shardingsphere.sharding.tables.b_order.actual-data-nodes=ds${0..1}.b_order${0..1}#spring.shardingsphere.sharding.tables.b_order.key-generator.column=id#spring.shardingsphere.sharding.tables.b_order.key-generator.type=SNOWFLAKE
代码实现
启动类
package com.galaxy;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.transaction.annotation.EnableTransactionManagement;/*** @author lane* @date 2021年07月20日 下午7:31*/@SpringBootApplication@EnableTransactionManagementpublic class RunBootApplication {/*public static void main(String[] args) {SpringApplication.run(RunBootApplication.class,args);}*/}
测试类
/*** @author lane* @date 2021年07月22日 下午4:43*/@RunWith(SpringRunner.class)@SpringBootTest(classes = RunBootApplication.class)public class TestShardingTransaction {@Resourceprivate PositionRepository positionRepository;@Resourceprivate PositionDetailRepository positionDetailRepository;@Test// @Transactional// @ShardingTransactionType(TransactionType.XA)public void test1(){// TransactionTypeHolder.set(TransactionType.XA);// for (int i=1;i<=5;i++){int i=1;Position position = new Position();position.setName("root"+i);position.setSalary("1000000");position.setCity("beijing");positionRepository.save(position);if (i==2){throw new RuntimeException("人为制造异常");}PositionDetail positionDetail = new PositionDetail();positionDetail.setPid(position.getId());positionDetail.setDescription("this is a root "+i);positionDetailRepository.save(positionDetail);}}
正常情况的结果,两个不同的库都添加了一条数据

修改测试代码 i=2 后如下,此时会出现异常,一个库的 position 执行成功,另一个库 position_detail 执行失败
/*** @author lane* @date 2021年07月22日 下午4:43*/@RunWith(SpringRunner.class)@SpringBootTest(classes = RunBootApplication.class)public class TestShardingTransaction {@Resourceprivate PositionRepository positionRepository;@Resourceprivate PositionDetailRepository positionDetailRepository;@Test@Transactional//@ShardingTransactionType(TransactionType.XA)public void test1(){// TransactionTypeHolder.set(TransactionType.XA);// for (int i=1;i<=5;i++){int i=2;Position position = new Position();position.setName("root"+i);position.setSalary("1000000");position.setCity("beijing");positionRepository.save(position);if (i==2){throw new RuntimeException("人为制造异常");}PositionDetail positionDetail = new PositionDetail();positionDetail.setPid(position.getId());positionDetail.setDescription("this is a root "+i);positionDetailRepository.save(positionDetail);}}
添加异常后的结果

删除刚才的数据,再次修改添加如下代码
TransactionTypeHolder.set(TransactionType.XA);
再次测试,可以看到成功回滚了


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