读写分离: Sharding-JDBC 读写分离配置与 Bean 定义冲突解决
纲要
- 准备数据库环境:在主库建 rw 库与 user 表,靠主从复制自动同步到从库
- 三步配置:导入坐标 → 配置 application.yml 读写分离规则 → 加 allow-bean-definition-overriding
- Bean 定义冲突:Sharding-JDBC 与 Druid 都要创建 DataSource,为什么会冲突、如何解决
- 验证路由:断点观察 MasterSlaveDataSource,日志确认 SELECT 走 slave、INSERT 走 master
- 踩坑:user 表 id 用 int 导致雪花 ID 越界,需改为 bigint
#mermaid-svg-fxxxXgffTmE5sttV{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fxxxXgffTmE5sttV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fxxxXgffTmE5sttV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fxxxXgffTmE5sttV .error-icon{fill:#552222;}#mermaid-svg-fxxxXgffTmE5sttV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fxxxXgffTmE5sttV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fxxxXgffTmE5sttV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fxxxXgffTmE5sttV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fxxxXgffTmE5sttV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fxxxXgffTmE5sttV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fxxxXgffTmE5sttV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fxxxXgffTmE5sttV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fxxxXgffTmE5sttV .marker.cross{stroke:#333333;}#mermaid-svg-fxxxXgffTmE5sttV svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fxxxXgffTmE5sttV p{margin:0;}#mermaid-svg-fxxxXgffTmE5sttV .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-fxxxXgffTmE5sttV .cluster-label text{fill:#333;}#mermaid-svg-fxxxXgffTmE5sttV .cluster-label span{color:#333;}#mermaid-svg-fxxxXgffTmE5sttV .cluster-label span p{background-color:transparent;}#mermaid-svg-fxxxXgffTmE5sttV .label text,#mermaid-svg-fxxxXgffTmE5sttV span{fill:#333;color:#333;}#mermaid-svg-fxxxXgffTmE5sttV .node rect,#mermaid-svg-fxxxXgffTmE5sttV .node circle,#mermaid-svg-fxxxXgffTmE5sttV .node ellipse,#mermaid-svg-fxxxXgffTmE5sttV .node polygon,#mermaid-svg-fxxxXgffTmE5sttV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fxxxXgffTmE5sttV .rough-node .label text,#mermaid-svg-fxxxXgffTmE5sttV .node .label text,#mermaid-svg-fxxxXgffTmE5sttV .image-shape .label,#mermaid-svg-fxxxXgffTmE5sttV .icon-shape .label{text-anchor:middle;}#mermaid-svg-fxxxXgffTmE5sttV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fxxxXgffTmE5sttV .rough-node .label,#mermaid-svg-fxxxXgffTmE5sttV .node .label,#mermaid-svg-fxxxXgffTmE5sttV .image-shape .label,#mermaid-svg-fxxxXgffTmE5sttV .icon-shape .label{text-align:center;}#mermaid-svg-fxxxXgffTmE5sttV .node.clickable{cursor:pointer;}#mermaid-svg-fxxxXgffTmE5sttV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fxxxXgffTmE5sttV .arrowheadPath{fill:#333333;}#mermaid-svg-fxxxXgffTmE5sttV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fxxxXgffTmE5sttV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fxxxXgffTmE5sttV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fxxxXgffTmE5sttV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fxxxXgffTmE5sttV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fxxxXgffTmE5sttV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fxxxXgffTmE5sttV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fxxxXgffTmE5sttV .cluster text{fill:#333;}#mermaid-svg-fxxxXgffTmE5sttV .cluster span{color:#333;}#mermaid-svg-fxxxXgffTmE5sttV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fxxxXgffTmE5sttV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fxxxXgffTmE5sttV rect.text{fill:none;stroke-width:0;}#mermaid-svg-fxxxXgffTmE5sttV .icon-shape,#mermaid-svg-fxxxXgffTmE5sttV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fxxxXgffTmE5sttV .icon-shape p,#mermaid-svg-fxxxXgffTmE5sttV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fxxxXgffTmE5sttV .icon-shape .label rect,#mermaid-svg-fxxxXgffTmE5sttV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fxxxXgffTmE5sttV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fxxxXgffTmE5sttV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fxxxXgffTmE5sttV :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
BeanDefinitionOverrideException
成功
主库建 rw 库与 user 表
主从复制自动同步到从库
导入 sharding-jdbc-spring-boot-starter
配置 datasource / masterslave / props.sql.show
启动
配置 allow-bean-definition-overriding: true
启动成功创建 2 个数据源
Postman 验证 SELECT → slaveINSERT → master
一、准备数据库环境
在主库创建数据库
— 在主库 192.168.138.100 执行
CREATE DATABASE rw DEFAULT CHARACTER SET utf8mb4;
rw 即 read-write(读写)。由于主从复制已配置,从库会自动同步出同名数据库——不需要在从库手工创建。
创建 user 表
USE rw;
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT,
address VARCHAR(200)
);
课程里的踩坑:最初 id 建成了 INT,插入时报 Out of range value for column 'id'。原因是 MyBatis-Plus 的 ASSIGN_ID 策略生成 19 位雪花 ID,远超 INT 的最大值 2147483647。改成 BIGINT 后正常。
修改后从库也自动跟着变了(因为 ALTER TABLE 也在 binlog 里)——这个现象本身就是主从复制生效的证明。
字段规划:
| id | BIGINT | 主键。用雪花 ID 时必须 BIGINT,INT 越界 |
| name | VARCHAR(50) | 姓名 |
| age | INT | 年龄 |
| address | VARCHAR(200) | 地址 |
二、导入初始工程
课程提供了 rw_demo 初始工程。它的特点:
| pom.xml | 常规 Spring Boot 依赖,尚未引入 Sharding-JDBC |
| application.yml | 只有端口与 mybatis-plus 配置,没有数据源配置 |
| User / UserMapper / UserService | 标准 MyBatis-Plus 三层 |
| UserController | 增删改查五个方法,注入了 DataSource(用于调试观察) |
为什么初始工程不配数据源?
因为数据源将由 Sharding-JDBC 提供。Sharding-JDBC 的自动配置类会读取 spring.shardingsphere.* 配置并创建 MasterSlaveDataSource,注册为 DataSource 类型的 Bean。如果此时还写了 spring.datasource.*,反而会冲突。
三、第一步:导入 Maven 坐标
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
四、第二步:配置读写分离规则
spring:
shardingsphere:
datasource:
names: master,slave
# 主库数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.100:3306/rw?characterEncoding=utf–8
username: root
password: root
# 从库数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.101:3306/rw?characterEncoding=utf–8
username: root
password: root
masterslave:
# 从库负载均衡策略:轮询
load-balance-algorithm-type: round_robin
# 最终数据源的名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个用逗号分隔
slave-data-source-names: slave
props:
sql:
show: true # 开启 SQL 显示,默认 false
main:
allow-bean-definition-overriding: true
配置项逐项拆解
datasource.names
names: master,slave
声明有两个数据源,名称分别为 master 与 slave。这两个名字可以随便起——叫 ds0、ds1 也行,只要上下对应:
# 合法示例:改了名字,下面所有引用都要跟着改
names: primary,replica
primary:
url: jdbc:mysql://192.168.138.100:3306/rw
replica:
url: jdbc:mysql://192.168.138.101:3306/rw
masterslave:
master-data-source-name: primary
slave-data-source-names: replica
注意:名字叫 master 不代表它就是主库。真正决定谁是主库的是 master-data-source-name。
datasource.master / datasource.slave
两个数据源的连接信息。与普通 Spring Boot 数据源配置的区别:
| 前缀 | spring.shardingsphere.datasource.<name> | spring.datasource |
| 驱动 | driver-class-name | driver-class-name |
| 连接池 | 需显式指定 type | 自动推断 |
type: com.alibaba.druid.pool.DruidDataSource 显式指定用 Druid 连接池。不指定的话 Sharding-JDBC 4.x 默认用 HikariCP(如果 classpath 里有)。
masterslave
| load-balance-algorithm-type | round_robin | 从库轮询(也可 random) |
| name | dataSource | 对外暴露的 DataSource Bean 名称 |
| master-data-source-name | master | 指定哪个是主库 |
| slave-data-source-names | slave | 指定哪些是从库,多个逗号分隔 |
多从库的写法:
datasource:
names: master,slave1,slave2
master:
url: jdbc:mysql://192.168.138.100:3306/rw
slave1:
url: jdbc:mysql://192.168.138.101:3306/rw
slave2:
url: jdbc:mysql://192.168.138.102:3306/rw
masterslave:
master-data-source-name: master
slave-data-source-names: slave1,slave2
load-balance-algorithm-type: round_robin
三个数据源,一主两从,读请求在 slave1 与 slave2 之间轮询。
props.sql.show
props:
sql:
show: true
开启后控制台会打印 SQL 以及它实际路由到的数据源名称,这是验证读写分离最直接的手段:
[main] INFO ShardingSphere-SQL – Rule Type: master-slave
[main] INFO ShardingSphere-SQL – SQL: SELECT … FROM user WHERE id=? ::: DataSources: slave
main.allow-bean-definition-overriding
见下一节。
五、第三步:解决 Bean 定义冲突
报错现象
配完 yml 直接启动,报:
org.springframework.beans.factory.support.BeanDefinitionOverrideException:
Invalid bean definition with name 'dataSource' defined in class path resource
[org/apache/shardingsphere/shardingjdbc/spring/boot/SpringBootConfiguration.class]:
Cannot register bean definition … since there is already
[Root bean: class [com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure]] bound.
根因
#mermaid-svg-7HLdmLL518keRkYb{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7HLdmLL518keRkYb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7HLdmLL518keRkYb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7HLdmLL518keRkYb .error-icon{fill:#552222;}#mermaid-svg-7HLdmLL518keRkYb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7HLdmLL518keRkYb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7HLdmLL518keRkYb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7HLdmLL518keRkYb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7HLdmLL518keRkYb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7HLdmLL518keRkYb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7HLdmLL518keRkYb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7HLdmLL518keRkYb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7HLdmLL518keRkYb .marker.cross{stroke:#333333;}#mermaid-svg-7HLdmLL518keRkYb svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7HLdmLL518keRkYb p{margin:0;}#mermaid-svg-7HLdmLL518keRkYb .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-7HLdmLL518keRkYb .cluster-label text{fill:#333;}#mermaid-svg-7HLdmLL518keRkYb .cluster-label span{color:#333;}#mermaid-svg-7HLdmLL518keRkYb .cluster-label span p{background-color:transparent;}#mermaid-svg-7HLdmLL518keRkYb .label text,#mermaid-svg-7HLdmLL518keRkYb span{fill:#333;color:#333;}#mermaid-svg-7HLdmLL518keRkYb .node rect,#mermaid-svg-7HLdmLL518keRkYb .node circle,#mermaid-svg-7HLdmLL518keRkYb .node ellipse,#mermaid-svg-7HLdmLL518keRkYb .node polygon,#mermaid-svg-7HLdmLL518keRkYb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7HLdmLL518keRkYb .rough-node .label text,#mermaid-svg-7HLdmLL518keRkYb .node .label text,#mermaid-svg-7HLdmLL518keRkYb .image-shape .label,#mermaid-svg-7HLdmLL518keRkYb .icon-shape .label{text-anchor:middle;}#mermaid-svg-7HLdmLL518keRkYb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7HLdmLL518keRkYb .rough-node .label,#mermaid-svg-7HLdmLL518keRkYb .node .label,#mermaid-svg-7HLdmLL518keRkYb .image-shape .label,#mermaid-svg-7HLdmLL518keRkYb .icon-shape .label{text-align:center;}#mermaid-svg-7HLdmLL518keRkYb .node.clickable{cursor:pointer;}#mermaid-svg-7HLdmLL518keRkYb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7HLdmLL518keRkYb .arrowheadPath{fill:#333333;}#mermaid-svg-7HLdmLL518keRkYb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7HLdmLL518keRkYb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7HLdmLL518keRkYb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7HLdmLL518keRkYb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7HLdmLL518keRkYb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7HLdmLL518keRkYb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7HLdmLL518keRkYb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7HLdmLL518keRkYb .cluster text{fill:#333;}#mermaid-svg-7HLdmLL518keRkYb .cluster span{color:#333;}#mermaid-svg-7HLdmLL518keRkYb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7HLdmLL518keRkYb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7HLdmLL518keRkYb rect.text{fill:none;stroke-width:0;}#mermaid-svg-7HLdmLL518keRkYb .icon-shape,#mermaid-svg-7HLdmLL518keRkYb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7HLdmLL518keRkYb .icon-shape p,#mermaid-svg-7HLdmLL518keRkYb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7HLdmLL518keRkYb .icon-shape .label rect,#mermaid-svg-7HLdmLL518keRkYb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7HLdmLL518keRkYb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7HLdmLL518keRkYb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7HLdmLL518keRkYb :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
false(默认)
true
应用启动
Sharding-JDBC 的SpringBootConfiguration
Druid 的DruidDataSourceAutoConfigure
@Bean dataSource创建 MasterSlaveDataSource
@Bean dataSource创建 DruidDataSource
两个 Bean 同名都是 dataSource
allow-bean-definition-overriding
BeanDefinitionOverrideException
后者覆盖前者启动成功
两个自动配置类都要创建名为 dataSource 的 Bean:
| Sharding-JDBC | SpringBootConfiguration | MasterSlaveDataSource |
| Druid starter | DruidDataSourceAutoConfigure | DruidDataSource |
Spring Boot 2.1 起,allow-bean-definition-overriding 的默认值从 true 改成了 false——不允许 Bean 定义覆盖,重复定义直接抛异常(2.0 及以前只是打一条 WARN 日志)。
解决方案
spring:
main:
allow-bean-definition-overriding: true
允许后创建的 Bean 覆盖先创建的。由于 Sharding-JDBC 的配置类排在 Druid 之后加载,最终容器里的 dataSource 是 MasterSlaveDataSource——这正是我们想要的。
更彻底的方案:排除 Druid 自动配置
如果不希望靠"覆盖"这种隐式行为,可以在启动类上排除 Druid 的数据源自动配置:
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
两种方式对比:
| allow-bean-definition-overriding: true | 配置简单,一行搞定 | 全局放开覆盖,可能掩盖其他真实的 Bean 冲突 |
| exclude = DruidDataSourceAutoConfigure.class | 精准排除,语义清晰 | 需要知道具体类名;Druid 的监控功能会受影响 |
课程用前者。生产建议后者——全局放开 Bean 覆盖是个危险开关,会让你在真正出现意外覆盖时也收不到报错。
Druid 监控页:如果排除了 DruidDataSourceAutoConfigure,Druid 的 StatViewServlet(/druid/index.html)需要单独配置。但注意:使用 Sharding-JDBC 后,Druid 监控看到的是两个内部数据源(master/slave),而不是那个 MasterSlaveDataSource。
六、启动验证
配置完成后启动,日志中可以看到:
[main] INFO … – Data sources: 2
[main] INFO … – datasource-1: DruidDataSource (jdbc:mysql://192.168.138.100:3306/rw)
[main] INFO … – datasource-2: DruidDataSource (jdbc:mysql://192.168.138.101:3306/rw)
创建了两个数据源,分别连主库和从库。
七、功能测试
测试一:查询走从库
在 UserController.getById 打断点,Postman 发:
GET http://localhost:8080/user/12345
观察注入的 DataSource
断点停住后,把光标放在 dataSource 上,可以看到:
| 实际类型 | MasterSlaveDataSource(Sharding-JDBC 提供) |
| dataSourceMap | size = 2,包含 master 与 slave |
| master 的 jdbcUrl | jdbc:mysql://192.168.138.100:3306/rw |
| slave 的 jdbcUrl | jdbc:mysql://192.168.138.101:3306/rw |
属性嵌套很深,需要逐层展开查找 jdbcUrl。这个观察证明了:业务代码注入的 DataSource 已经不是 Druid 的了,而是 Sharding-JDBC 封装的读写分离数据源。
观察控制台日志
放行后控制台输出:
ShardingSphere-SQL : Rule Type: master-slave
ShardingSphere-SQL : SQL: SELECT id,name,age,address FROM user WHERE id=? ::: DataSources: slave
DataSources: slave —— 查询确实走了从库。
测试二:新增走主库
POST http://localhost:8080/user
Content-Type: application/x-www-form-urlencoded
name=张三&age=20
控制台:
ShardingSphere-SQL : Rule Type: master-slave
ShardingSphere-SQL : SQL: INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? ) ::: DataSources: master
DataSources: master —— 写操作走主库。
测试三:数据同步验证
#mermaid-svg-LMwpLVJ9nIHYtVag{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LMwpLVJ9nIHYtVag .error-icon{fill:#552222;}#mermaid-svg-LMwpLVJ9nIHYtVag .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LMwpLVJ9nIHYtVag .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LMwpLVJ9nIHYtVag .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LMwpLVJ9nIHYtVag .marker.cross{stroke:#333333;}#mermaid-svg-LMwpLVJ9nIHYtVag svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LMwpLVJ9nIHYtVag p{margin:0;}#mermaid-svg-LMwpLVJ9nIHYtVag .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag .cluster-label text{fill:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag .cluster-label span{color:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag .cluster-label span p{background-color:transparent;}#mermaid-svg-LMwpLVJ9nIHYtVag .label text,#mermaid-svg-LMwpLVJ9nIHYtVag span{fill:#333;color:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag .node rect,#mermaid-svg-LMwpLVJ9nIHYtVag .node circle,#mermaid-svg-LMwpLVJ9nIHYtVag .node ellipse,#mermaid-svg-LMwpLVJ9nIHYtVag .node polygon,#mermaid-svg-LMwpLVJ9nIHYtVag .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LMwpLVJ9nIHYtVag .rough-node .label text,#mermaid-svg-LMwpLVJ9nIHYtVag .node .label text,#mermaid-svg-LMwpLVJ9nIHYtVag .image-shape .label,#mermaid-svg-LMwpLVJ9nIHYtVag .icon-shape .label{text-anchor:middle;}#mermaid-svg-LMwpLVJ9nIHYtVag .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LMwpLVJ9nIHYtVag .rough-node .label,#mermaid-svg-LMwpLVJ9nIHYtVag .node .label,#mermaid-svg-LMwpLVJ9nIHYtVag .image-shape .label,#mermaid-svg-LMwpLVJ9nIHYtVag .icon-shape .label{text-align:center;}#mermaid-svg-LMwpLVJ9nIHYtVag .node.clickable{cursor:pointer;}#mermaid-svg-LMwpLVJ9nIHYtVag .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LMwpLVJ9nIHYtVag .arrowheadPath{fill:#333333;}#mermaid-svg-LMwpLVJ9nIHYtVag .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LMwpLVJ9nIHYtVag .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LMwpLVJ9nIHYtVag .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LMwpLVJ9nIHYtVag .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LMwpLVJ9nIHYtVag .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LMwpLVJ9nIHYtVag .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LMwpLVJ9nIHYtVag .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LMwpLVJ9nIHYtVag .cluster text{fill:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag .cluster span{color:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LMwpLVJ9nIHYtVag .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LMwpLVJ9nIHYtVag rect.text{fill:none;stroke-width:0;}#mermaid-svg-LMwpLVJ9nIHYtVag .icon-shape,#mermaid-svg-LMwpLVJ9nIHYtVag .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LMwpLVJ9nIHYtVag .icon-shape p,#mermaid-svg-LMwpLVJ9nIHYtVag .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LMwpLVJ9nIHYtVag .icon-shape .label rect,#mermaid-svg-LMwpLVJ9nIHYtVag .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LMwpLVJ9nIHYtVag .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LMwpLVJ9nIHYtVag .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LMwpLVJ9nIHYtVag :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
主从复制 binlog 同步
POST /userINSERT 走 master
主库 user 表有新数据
从库 user 表也有新数据
GET /user/{id} 走 slave能查到刚插入的数据
查询主库:SELECT * FROM user 能看到新记录 ✅
查询从库:SELECT * FROM user 也能看到 ✅(主从复制同步过来的)
这说明读写分离 + 主从复制配合工作:写主库 → 自动同步到从库 → 读从库能查到。
八、完整代码
application.yml(完整版)
server:
port: 8080
spring:
shardingsphere:
datasource:
# 数据源名称列表,逗号分隔
names: master,slave
# 主库数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.100:3306/rw?characterEncoding=utf–8
username: root
password: root
# 从库数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.138.101:3306/rw?characterEncoding=utf–8
username: root
password: root
masterslave:
# 从库负载均衡策略:round_robin 轮询 / random 随机
load-balance-algorithm-type: round_robin
# 最终对外暴露的数据源名称
name: dataSource
# 主库数据源名称(对应 datasource 下定义的名字)
master-data-source-name: master
# 从库数据源名称列表,多个用逗号分隔
slave-data-source-names: slave
props:
sql:
show: true # 打印 SQL 及路由到的数据源,便于验证
# 允许 Bean 定义覆盖:解决 Sharding-JDBC 与 Druid 都创建 dataSource 的冲突
main:
allow-bean-definition-overriding: true
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID # 雪花算法生成 ID,需 19 位,数据库字段必须 BIGINT
Application(排除 Druid 自动配置的写法)
package com.itheima;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 读写分离案例启动类
*
* <p>exclude = DruidDataSourceAutoConfigure.class 的作用:
* 排除 Druid 的数据源自动配置,避免它与 Sharding-JDBC 的
* SpringBootConfiguration 争抢名为 dataSource 的 Bean。
* 这是比 allow-bean-definition-overriding=true 更精准的做法。</p>
*
* <p>注意:如果采用排除方式,yml 中的 allow-bean-definition-overriding 可以去掉。</p>
*/
@Slf4j
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
log.info("读写分离案例启动成功…");
}
}
建表 SQL
— 在主库 192.168.138.100 执行,从库会通过主从复制自动同步
CREATE DATABASE IF NOT EXISTS rw DEFAULT CHARACTER SET utf8mb4;
USE rw;
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id BIGINT(20) NOT NULL COMMENT '主键,雪花ID必须BIGINT',
name VARCHAR(50) DEFAULT NULL COMMENT '姓名',
age INT(11) DEFAULT NULL COMMENT '年龄',
address VARCHAR(200) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
验证脚本
#!/bin/bash
# 读写分离验证脚本
echo "===== 1. 新增(应走 master) ====="
curl -X POST "http://localhost:8080/user" -d "name=张三" -d "age=20" -d "address=北京"
echo ""
echo "===== 2. 查询列表(应走 slave) ====="
curl "http://localhost:8080/user/list?name=张三"
echo ""
echo "===== 3. 主库查询 ====="
mysql -h 192.168.138.100 -uroot -proot -e "SELECT * FROM rw.user;"
echo "===== 4. 从库查询(应与主库一致) ====="
mysql -h 192.168.138.101 -uroot -proot -e "SELECT * FROM rw.user;"
九、常见问题
| BeanDefinitionOverrideException | Druid 与 Sharding-JDBC 抢 dataSource | 加 allow-bean-definition-overriding: true 或排除 DruidDataSourceAutoConfigure |
| Out of range value for column 'id' | id 字段是 INT,雪花 ID 越界 | ALTER TABLE user MODIFY id BIGINT; |
| 启动时报数据源连接失败 | IP/库名/账号密码错 | 用 mysql -h IP -u user -p 实测连通性 |
| 所有 SQL 都走 master | 方法上有 @Transactional;或 SELECT … FOR UPDATE | 检查事务注解范围 |
| 从库查不到刚插入的数据 | 主从延迟 | 用 HintManager 强制走主库;或检查 Seconds_Behind_Master |
| DataSources 日志不打印 | props.sql.show 未设为 true | 检查配置层级是否为 shardingsphere.props.sql.show |
| Slave_IO_Running: No | 主从复制断了 | 重新配置 CHANGE MASTER TO(见第 58 篇) |
API 速览
| sharding-jdbc-spring-boot-starter | Maven 坐标 | Sharding-JDBC starter(4.1.1) |
| spring.shardingsphere.datasource.names | application.yml | 数据源名称列表 |
| spring.shardingsphere.datasource.<name>.type | application.yml | 连接池实现类 |
| spring.shardingsphere.datasource.<name>.url | application.yml | JDBC 连接串 |
| spring.shardingsphere.masterslave.name | application.yml | 对外数据源 Bean 名 |
| spring.shardingsphere.masterslave.master-data-source-name | application.yml | 指定主库 |
| spring.shardingsphere.masterslave.slave-data-source-names | application.yml | 指定从库(多个逗号分隔) |
| spring.shardingsphere.masterslave.load-balance-algorithm-type | application.yml | round_robin / random |
| spring.shardingsphere.props.sql.show | application.yml | 打印 SQL 及路由数据源 |
| spring.main.allow-bean-definition-overriding | application.yml | 允许 Bean 定义覆盖 |
| @SpringBootApplication(exclude=…) | org.springframework.boot.autoconfigure | 排除指定自动配置类 |
| DruidDataSourceAutoConfigure | com.alibaba.druid.spring.boot.autoconfigure | Druid 数据源自动配置类 |
| MasterSlaveDataSource | org.apache.shardingsphere.shardingjdbc.jdbc.core.datasource | 读写分离数据源 |
| getDataSourceMap() | MasterSlaveDataSource | 获取内部数据源 Map |
| id-type: ASSIGN_ID | mybatis-plus.global-config.db-config | 雪花算法 ID 生成策略 |
官方文档
| Sharding-JDBC 读写分离(4.x) | https://shardingsphere.apache.org/document/4.1.1/cn/manual/sharding-jdbc/usage/read-write-splitting/ |
| Spring Boot 配置项 | https://shardingsphere.apache.org/document/4.1.1/cn/manual/sharding-jdbc/configuration/config-spring-boot/ |
| Spring Boot 2.1 Bean 覆盖变更 | https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1-Release-Notes#bean-overriding |
| MyBatis-Plus ID 生成策略 | https://baomidou.com/pages/e131bd/ |
总结
Sharding-JDBC 实现读写分离只需要三步,全程不写一行 Java 代码:导坐标、配 yml、允许 Bean 覆盖。
配置的核心是把三件事说清楚:有几个数据源(datasource.names)、每个数据源连哪里(datasource.<name>.*)、谁是主谁是从(masterslave.master-data-source-name / slave-data-source-names)。数据源名字可以随便起,但必须上下对应——名字叫 master 不代表它是主库,真正决定身份的是 master-data-source-name。
Bean 定义冲突是个必踩的坑:Sharding-JDBC 的 SpringBootConfiguration 和 Druid 的 DruidDataSourceAutoConfigure 都要创建名为 dataSource 的 Bean,而 Spring Boot 2.1+ 默认禁止覆盖。两种解法——加 allow-bean-definition-overriding: true(课程做法,简单),或 @SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)(推荐,精准且不会掩盖其他真实冲突)。
验证方法有两条:断点观察注入的 DataSource 已变成 MasterSlaveDataSource 且 dataSourceMap.size = 2;控制台日志里 DataSources: slave(SELECT)与 DataSources: master(INSERT),这是最直接的证据。
踩坑提醒:user.id 必须用 BIGINT。MyBatis-Plus 的 ASSIGN_ID 生成 19 位雪花 ID,INT 字段会 Out of range。
最后重申一个容易让读写分离失效的机制:事务内所有 SQL 都走主库。验证时如果看到 SELECT 也走 master,先查方法上有没有 @Transactional。
下一篇把读写分离改造落到外卖平台主项目上。
网硕互联帮助中心



评论前必须登录!
注册