代码地址
Springboot:2.2.5.RELEASE
com.alibaba:druid-spring-boot-starter:1.1.21
io.shardingsphere:sharding-jdbc-spring-boot-starter:3.1.0
数据库权限:
用户 | tp数据库(查) | tp数据库(增) | tp1数据库(查) | tp1数据库(增) | tp2数据库(查) | tp2数据库(增) |
---|---|---|---|---|---|---|
tp | √ | √ | × | × | × | × |
tp1 | × | × | × | × | × | √ |
tp2 | × | × | × | × | × | √ |
数据库表结构:
tp、tp1、tp2数据下的test_user、test_user0、test_user1结构均一样。
项目结构预览
基于shardingsphere,无论是实现的分库分表还是读写分离,对业务代码均没有侵入,因此这里先把基础的代码列出
application.yml
spring:
profiles:
#[单库单表]
#active: single
#[读写分离]
active: masterslave
#[分库分表]
#active: strategy
#[读写分离+分库分表]
#active: masterslavestrategy
main:
allow-bean-definition-overriding: true
server:
servlet:
context-path: /tp
port: 8806
mybatis:
mapper-locations: classpath:mapping/*Mapper.xml
type-aliases-package: cn.com.test.sharding.model
log4j:
rootLogger: debug,stdout
config: classpath:log4j2.xml
实体TestUser.java
package cn.com.test.sharding.model;
import com.alibaba.fastjson.JSON;
import java.util.Date;
public class TestUser {
public Integer id;
public String db;
public String userId;
public Integer sex;
public String remark;
public Date modifyDate;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDb() {
return db;
}
public void setDb(String db) {
this.db = db;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
Dao操作接口TestUserMapper.java
package cn.com.test.sharding.dao;
import cn.com.test.sharding.model.TestUser;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TestUserMapper {
void insert(TestUser user);
List<TestUser> list();
List<TestUser> listByremark(String remark);
List<TestUser> listByremarkLimit2(String remark);
}
SQL操作 TestUserMapper.xml
<mapper namespace="cn.com.test.sharding.dao.TestUserMapper">
<resultMap id="baseResultMap" type="cn.com.test.sharding.model.TestUser">
<id column="id" jdbcType="INTEGER" property="id"/>
<id column="db" jdbcType="VARCHAR" property="db"/>
<result column="user_id" jdbcType="VARCHAR" property="userId"/>
<result column="sex" jdbcType="INTEGER" property="sex"/>
<result column="remark" jdbcType="VARCHAR" property="remark"/>
<result column="modify_time" jdbcType="TIMESTAMP" property="modifyDate"/>
resultMap>
<sql id="base_column_list">
id,db,user_id,sex,remark,modify_time
sql>
<insert id="insert" parameterType="cn.com.test.sharding.model.TestUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into test_user (
<include refid="base_column_list"/>
)
value (
#{id,jdbcType=INTEGER},#{db,jdbcType=VARCHAR},#{userId,jdbcType=VARCHAR},#{sex,jdbcType=INTEGER},#{remark,jdbcType=VARCHAR},current_timestamp
);
insert>
<select id="list" resultMap="baseResultMap">
select
<include refid="base_column_list"/>
from test_user
where 1=1
select>
<select id="listByremark" resultMap="baseResultMap" parameterType="string">
select
<include refid="base_column_list"/>
from test_user
where remark like #{remark,jdbcType=VARCHAR}
select>
<select id="listByremarkLimit2" resultMap="baseResultMap" parameterType="string">
select
<include refid="base_column_list"/>
from test_user
where remark like #{remark,jdbcType=VARCHAR}
limit 2
select>
mapper>
操作层 TestController.xml
package cn.com.test.sharding.controller;
import cn.com.test.sharding.dao.TestUserMapper;
import cn.com.test.sharding.model.TestUser;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/test")
public class TestController {
static Logger logger = LoggerFactory.getLogger(TestController.class);
@Autowired
TestUserMapper testUserMapper;
@GetMapping("/user")
public String user(TestUser user){
logger.info("请求:"+user);
user.setUserId(UUID.randomUUID().toString());
testUserMapper.insert(user);
logger.info(user.toString());
List<TestUser> userList = testUserMapper.list();
logger.info("all查询结果:"+userList.size()+"\t"+JSON.toJSONString(userList));
List<TestUser> userList1 = testUserMapper.listByremark(user.getRemark()+"%");
logger.info("remark查询结果:"+userList1.size()+"\t"+JSON.toJSONString(userList1));
List<TestUser> userList2 = testUserMapper.listByremarkLimit2(user.getRemark()+"%");
logger.info("remark查询(限定查询2条)结果:"+userList2.size()+"\t"+JSON.toJSONString(userList2));
return JSON.toJSONString(userList);
}
}
常规用法,既不分库也不分表;读写在一个库上进行
application-single.yml配置:
# 不读写分离也不分表分库
spring:
servlet:
multipart:
max-file-size: 1MB
max-request-size: 2MB
datasource:
username: tp
password: tp@123
url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 2
minIdle: 2
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUALco
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
druid.filters: stat,wall
maxPoolPreparedStatementPerConnectionSize: 2
pom.xml引入
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
<relativePath/>
parent>
<groupId>com.testgroupId>
<artifactId>sprintboot-shardingartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>warpackaging>
<name>sharding-demonname>
<description>Spring-boot shardingdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-loggingartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.21version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.9version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
<excludeDevtools>trueexcludeDevtools>
<excludes>
<exclude>
<artifactId>spring-boot-starter-tomcatartifactId>
<groupId>org.springframework.bootgroupId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
常规情况
测试url:localhost:8806/tp/test/user?sex=0&remark=DD&db=0
分析:
这种情况比较容易理解,数据库操作对象清晰可见,数据库:tp,表:test_user
测试url:http://localhost:8806/tp/test/user?sex=0&remark=DD&db=0
代码执行结果:
数据库:
读写在不同的数据库上
采用策略:在tp上写,在tp1上读 表:test_user
预期结果:tp上数据正常写入,tp1读取到测试数据
application-masterslave.yml配置:
sharding:
jdbc:
datasource:
names: master,slave
master:
username: tp
password: tp@123
url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
slave:
username: tp1
password: tp1@123
url: jdbc:mysql://192.168.1.16:3308/tp1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
config:
# 主从库配置 操作表名不会跟上数字(test_user)
masterslave:
# 负载均衡策略(load-balance-algorithm-class-name: 指定自定义策略,实现MasterSlaveLoadBalanceAlgorithm且提供无参构造)
load-balance-algorithm-type: random
master-data-source-name: master
slave-data-source-names: slave
name: demo1
# 读写分离的实验结果:读库没有数据,所以数据可以正常写入,但是读出结果为空
相对与读写分离而言,pom无需变化
无论读写,根据db决策使用的数据库,根据sex%2决策使用的表
采用策略:数据库:tp d b 表 : t e s t u s e r {db} 表:test_user db表:testuser{sex % 2}
预期结果:{db=0,sex=1}写入tp.test_user1
{db=0,sex=2}写入tp.test_user0
{db=1,sex=3}写入tp1.test_user1
{db=1,sex=4}写入tp1.test_user0
application-strategy.yml
spring:
main:
allow-bean-definition-overriding: true
sharding:
jdbc:
datasource:
names: ds0,ds1
ds0:
username: tp
password: tp@123
url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
ds1:
username: tp1
password: tp1@123
url: jdbc:mysql://192.168.1.16:3308/tp1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
config:
sharding:
props:
sql.show: true
#分表分库策略 操作带数字的表和数据源ds[0,1].test_user[0,1]
tables:
test_user:
# 自增主键,这里声明后mapper.xml中返回主键不再可用
# key-generator-column-name: id
# 数据节点,均匀分布 单一数据源去除数据库后的表达式即可 eg: ds$.test_user${0..1}
actual-data-nodes: ds${
0..1}.test_user${
0..1}
#分库策略
database-strategy:
#行表达式
inline:
#列名称,多个列以逗号分隔
sharding-column: db
#直接参数里指定了数据源
algorithm-expression: ds${
db}
#分表策略
table-strategy:
#行表达式
inline:
#分片列
sharding-column: sex
#分片表达式 对sex列取模
algorithm-expression: test_user${
sex % 2}
#自定义复杂分片
# complex:
# sharding-columns: #分片列名称,多个列以逗号分隔
# algorithm-class-name: #复合分片算法类名称。实现ComplexKeysShardingAlgorithm并提供无参数的构造器
#分库分表,根据db决策写入的数据库,根据sex决策写入的表,读取时应为所有来源之和
{db=0,sex=1}写入tp.test_user1
测试url:localhost:8806/tp/test/user?sex=1&remark=DD&db=0
清空tp、tp1下的test_user0、test_user1
{db=0,sex=2}写入tp.test_user0
测试url:localhost:8806/tp/test/user?sex=2&remark=DD&db=0
执行结果(汇总上一种的情况进行查询,所以查询结果应该为2条):
tp.test_user0
{db=1,sex=3}写入tp1.test_user1
测试url:localhost:8806/tp/test/user?sex=3&remark=DD&db=1
执行结果(汇总上一种的情况进行查询,所以查询结果应该为3条):
tp1.test_user1
{db=1,sex=4}写入tp1.test_user0
测试url:localhost:8806/tp/test/user?sex=4&remark=DD&db=1
执行结果(汇总上一种的情况进行查询,所以查询结果应该为4条):
简单整理下对应关系~
条件db | 条件sex | 数据源 | 操作数据库 | 操作表 |
---|---|---|---|---|
0 | 1 | ds0 | tp | test_user1 |
0 | 2 | ds0 | tp | test_user0 |
1 | 3 | ds1 | tp1 | test_user1 |
1 | 4 | ds1 | tp1 | test_user0 |
相对与读写分离、分库分表而言,pom无需变化
读slave库,根据db决策查询的数据库,根据sex%2决策查询的表
写master库,根据db决策写入的数据库,根据sex%2决策写入的表
采用策略:数据库:tp d b 表 : t e s t u s e r {db} 表:test_user db表:testuser{sex % 2}
增加tp2并仅对tp2赋予对tp2库所有表的查询权限
预期结果:{db=0,sex=5}写入tp.test_user1 读取tp2两次
{db=0,sex=6}写入tp.test_user0 读取tp2两次
{db=1,sex=7}写入tp1.test_user1 读取tp2两次
{db=1,sex=8}写入tp1.test_user0 读取tp2两次
注意:db被拆分为两个表,所以读取和写入均在两个表之间决策和聚集
application-masterslavestrategy
sharding:
jdbc:
datasource:
names: master0,master1,master0-slave,master1-slave
master0:
username: tp
password: tp@123
url: jdbc:mysql://192.168.1.16:3308/tp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
master1:
username: tp1
password: tp1@123
url: jdbc:mysql://192.168.1.16:3308/tp1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
master0-slave:
username: tp2
password: tp2@123
url: jdbc:mysql://192.168.1.16:3308/tp2?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
master1-slave:
username: tp2
password: tp2@123
url: jdbc:mysql://192.168.1.16:3308/tp2?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
config:
sharding:
tables:
test_user:
# 自增主键,这里声明后mapper.xml中返回主键不再可用
# key-generator-column-name: id
# 数据节点,均匀分布 单一数据源去除数据库后的表达式即可 eg: ds$.test_user${0..1}
actual-data-nodes: ds${
0..1}.test_user${
0..1}
#分库策略
database-strategy:
#行表达式
inline:
#列名称,多个列以逗号分隔
sharding-column: db
#直接参数里指定了数据源
algorithm-expression: ds${
db}
#分表策略
table-strategy:
#行表达式
inline:
#分片列
sharding-column: sex
#分片表达式 对sex列取模
algorithm-expression: test_user${
sex % 2}
master-slave-rules:
ds0:
master-data-source-name: master0
slave-data-source-names: master0-slave
ds1:
master-data-source-name: master1
slave-data-source-names: master1-slave
测试过程省略,执行结果如下:
tp.test_user0
tp.test_user1
tp1.test_user0
tp1.test_user1
tp2.test_user0
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200408215603959.png
tp2.test_user1