云计算百科
云计算领域专业知识百科平台

Java 项目实战: 外卖平台优化-ShardingJDBC读写分离配置与Bean定义冲突

读写分离: 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 数据源配置的区别:

项Sharding-JDBC 写法普通 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:

来源配置类创建的 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 速览

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。

下一篇把读写分离改造落到外卖平台主项目上。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Java 项目实战: 外卖平台优化-ShardingJDBC读写分离配置与Bean定义冲突
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!