基于Docker实现MySql的主从复制 + SpringBoot、Mybatis-Plus实现读写分离【含源码】
MySql的主从复制
数据库读写分离对于大型系统或者访问量很高的互联网应用来说,是必不可少的一个重要功能。Insert SQL就是几十个毫秒,Select SQL则要几秒到几分钟才能有结果,很多复杂的SQL,其消耗服务器CPU的能力超强,不亚于死循环的威力。高峰时段的一些复杂SQL查询就导致数据库服务器CPU爆表,系统陷入瘫痪,严重情况下可能导致数据库崩溃。我们应该尽量避免没有主从复制机制的单节点数据库。
对于MySQL来说,标准的读写分离是主从模式,一个写节点Master后面跟着多个读节点,读节点的数量取决于系统的压力,通常是1-3个读节点的配置。
MySQL主从复制的原理
1.主库上记录二进制日志(稍后介绍如何设置)。在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中,MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。
2.备库将主库的二进制日志复制到其本地的中继日志中,备库会启动一个工作线程,称为IO线程,I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binhg dump、线程该线程没有对应的SQL命令),这个二进制转储线程会读取主库上二进制日志中的事件
3.备库的SQL线程执行最后一步,该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新主从复制是mysql来实现,mycat配合mysql的主从来完成功能。
环境搭建
拉取镜像
docker pull mysql
创建两个文件夹用于存储主机和从机的数据
D:\mysql\master\data
D:\mysql\slave\data
注:此教程是基于docker desktop环境搭建的,读者也可以通过Linux虚拟机搭建
主机配置
创建主机容器
docker run -d ^
--name mysql-master ^
-p 3307:3306 ^
-e MYSQL_ROOT_PASSWORD=123456 ^
-e TZ=Asia/Shanghai ^
-v D:\mysql\master\data:/var/lib/mysql ^
--restart unless-stopped ^
mysql:8.0 ^
--server-id=1 ^
--log-bin=mysql-bin ^
--binlog-format=ROW ^
--character-set-server=utf8mb4 ^
--collation-server=utf8mb4_general_ci ^
--lower_case_table_names=1

进入主库容器
docker exec -it mysql-master bash
# 登录mysql
mysql -uroot -p123456

创建主从同步专用账号
# 创建同步用户 slave,允许所有IP访问
CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
# 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
# 刷新权限
FLUSH PRIVILEGES;

查看主库状态(关键!记录参数)
SHOW MASTER STATUS;
一定要抄下:文件名 File、偏移 Position,后面从库要用
从机配置
创建从机容器
docker run -d ^
--name mysql-slave ^
-p 3308:3306 ^
-e MYSQL_ROOT_PASSWORD=123456 ^
-e TZ=Asia/Shanghai ^
-v D:\mysql\slave\data:/var/lib/mysql ^
--restart unless-stopped ^
mysql:8.0 ^
--server-id=2 ^
--relay-log=relay-bin ^
--log-bin=mysql-bin ^
--read-only=1 ^
--character-set-server=utf8mb4 ^
--collation-server=utf8mb4_general_ci ^
--lower_case_table_names=1

进入从库
docker exec -it mysql-slave bash
# 登录数据库
mysql -uroot -p123456

先停掉同步
STOP SLAVE;
RESET SLAVE ALL;

绑定主库(把 File 和 Position 换成你刚才看到的)
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='host.docker.internal',
SOURCE_PORT=3307,
SOURCE_USER='slave',
SOURCE_PASSWORD='123456',
SOURCE_LOG_FILE='mysql-bin.000003',
SOURCE_LOG_POS=860;

启动同步
START SLAVE;
查看状态
SHOW SLAVE STATUS\G;
关键成功标志解读
看到这两个 = 主从成功
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

如果不成功,请删除master、slave两个容器,重新搭建
docker rm -f mysql-master mysql-slave
测试
- 主库插入测试数据
-- 创建测试库和表
CREATE DATABASE test_sync_db;
USE test_sync_db;
CREATE TABLE user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
create_time DATETIME DEFAULT NOW()
);
-- 插入数据
INSERT INTO user_info (id, name) VALUES (1, '测试主从同步'), (2, 'MySQL 8.0');
- 从库验证数据
-- 查看同步的数据
USE test_sync_db;
SELECT * FROM user_info;
预期输出(和主库完全一致):
+----+------------------+---------------------+
| id | name | create_time |
+----+------------------+---------------------+
| 1 | 测试主从同步 | 2026-03-31 21:xx:xx |
| 2 | MySQL 8.0 | 2026-03-31 21:xx:xx |
+----+------------------+---------------------+
基于SpirngBoot、Mybatis-Plus的读写分离实现
核心步骤
SpringBoot 3.5.7 结合 MyBatis-Plus 实现 MySQL 主从读写分离的核心思路是基于动态数据源切换 + AOP 切面拦截,将写操作(新增、修改、删除)路由到主库,读操作(查询)路由到从库,充分利用 MySQL 主从复制的特性提升系统读写性能,具体实现逻辑可分为以下几个核心步骤:
1. 基础环境与依赖配置
首先搭建适配的技术环境:
- 引入 SpringBoot 3.5.7 核心依赖、MyBatis-Plus 3.5.8+(适配 Spring6.2 底层 API)、Druid 数据源(适配 SpringBoot 3)及 AOP 相关依赖,解决版本兼容性问题;
- 在配置文件(application.yml)中分别配置主库(写)和从库(读)的连接信息(URL、用户名、密码、驱动类),明确区分两类数据源的配置前缀。
2. 动态数据源核心实现
基于 Spring 提供的 AbstractRoutingDataSource 抽象类实现动态数据源切换,这是读写分离的核心底层支撑:
- 定义数据源类型枚举(MASTER/SLAVE),标识主库和从库;
- 实现 DynamicDataSource 类继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey() 方法,该方法会从线程上下文(ThreadLocal)中获取当前需要使用的数据源类型,作为数据源切换的核心依据;
- 构建 DataSourceContextHolder 工具类,通过 ThreadLocal 存储和管理当前线程的数据源类型(MASTER/SLAVE),保证多线程环境下数据源切换的线程安全,且在操作完成后清除 ThreadLocal 中的数据,避免内存泄漏。
3. 数据源配置与注入
将主从数据源注入 Spring 容器,并整合为动态数据源:
- 分别配置主库(masterDataSource)和从库(slaveDataSource)的 Druid 数据源 Bean,绑定配置文件中的连接信息;
- 构建动态数据源 Bean(dynamicDataSource),将主从数据源存入 targetDataSources 映射表,指定主库为默认数据源(保证未明确指定时默认走写库);
- 配置事务管理器和 MyBatis-Plus 的 SqlSessionFactory,指定动态数据源为底层数据源,确保 MyBatis-Plus 操作基于动态数据源执行。
4. AOP 切面实现读写路由拦截
通过自定义注解 + AOP 切面,实现业务方法层面的数据源自动切换:
- 定义自定义注解 @ReadWriteSplit,可指定注解值为 MASTER/SLAVE,用于标记方法对应的数据源类型;
- 编写 AOP 切面类 ReadWriteSplitAspect,以该注解为切入点,在方法执行前拦截并解析注解中的数据源类型,通过 DataSourceContextHolder 设置当前线程的数据源类型;
- 切面执行优先级高于事务切面,保证数据源切换先于事务生效;方法执行完成后(finally 块)清除线程上下文的数据源类型,避免线程复用导致的数据源错乱。
5. 业务层适配与验证
在业务代码中落地读写分离规则:
- 对新增、修改、删除等写操作方法,标记 @ReadWriteSplit(MASTER)(或默认使用主库),确保写操作路由到主库,保证数据一致性;
- 对查询类读操作方法,标记 @ReadWriteSplit(SLAVE),将读请求路由到从库,分担主库压力;
- 测试验证:写操作后数据同步到从库,读操作能从从库获取最新数据,且日志可清晰看到数据源切换过程,最终实现「写主库、读从库」的读写分离效果。
核心设计原则
- 线程安全:通过 ThreadLocal 隔离线程的数据源上下文,避免多线程环境下的数据源冲突;
- 兼容性:适配 SpringBoot 3.5.7 与 MyBatis-Plus 的版本兼容,手动配置 MyBatis-Plus 专属的 MybatisSqlSessionFactoryBean 避免类型解析错误;
- 可扩展:若需扩展多从库,可在 DataSourceContextHolder 中增加负载均衡逻辑(如轮询、随机),无需修改核心切换逻辑;
- 事务适配:事务内的所有操作强制走主库,避免读未提交的数据,保证事务一致性。
读写时序图
写时序图
读时序图
实现代码
代码仓库:https://gitee.com/xiangweilll/mysql-master-slave-replication.git
项目结构
核心依赖
<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.elvis</groupId>
<artifactId>mysql-master-slave-replication</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>mysql-master-slave-replication</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.5.7</spring-boot.version>
</properties>
<dependencies>
<!-- SpringBoot核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.12</version>
</dependency>
<!-- 数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
<compilerArgs>
<arg>-parameters</arg> <!--在编译代码是保留参数的名字-->
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.woniuxy.LangchainApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
主配置文件
spring:
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# 主库(写)
master:
url: jdbc:mysql://localhost:3307/test_sync_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库(读)
slave:
url: jdbc:mysql://localhost:3308/test_sync_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(调试用)
数据源枚举
配置主从库连接信息(对应搭建的 3307 主库、3308 从库):
package com.elvis.enums;
/**
* 数据源类型枚举
*/
public enum DataSourceType {
/**
* 主库(写操作)
*/
MASTER,
/**
* 从库(读操作)
*/
SLAVE
}
数据源上下文
用 ThreadLocal 存储当前线程的数据源类型:
package com.elvis.config;
import com.elvis.enums.DataSourceType;
/**
* 数据源上下文持有者(线程隔离)
*/
public class DataSourceContextHolder {
/**
* 线程本地存储数据源类型
*/
private static final ThreadLocal<DataSourceType> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源类型
*/
public static void setDataSourceType(DataSourceType type) {
CONTEXT_HOLDER.set(type);
}
/**
* 获取当前数据源类型
*/
public static DataSourceType getDataSourceType() {
// 默认使用主库
return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER : CONTEXT_HOLDER.get();
}
/**
* 清除数据源类型
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
动态数据源
核心类,实现数据源动态切换:
package com.elvis.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源(根据上下文切换主从库)
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 核心方法:返回当前线程对应的数据源key
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
数据源配置
配置主从数据源并注入动态数据源:
package com.elvis.config;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import com.elvis.enums.DataSourceType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 数据源配置类
*/
@Configuration
public class DataSourceConfig {
/**
* 配置主库数据源
*/
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置从库数据源
*/
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置动态数据源(主库为默认)
*/
@Bean(name = "dynamicDataSource")
@Primary // 优先使用该数据源
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 配置主从数据源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource());
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
// 设置数据源映射
dynamicDataSource.setTargetDataSources(targetDataSources);
// 设置默认数据源(主库)
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
/**
* 配置事务管理器
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
读写分离注解
自定义注解,标记方法使用主库 / 从库:
package com.elvis.annotation;
import com.elvis.enums.DataSourceType;
import java.lang.annotation.*;
/**
* 读写分离注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReadWriteSplit {
/**
* 数据源类型(默认主库)
*/
DataSourceType value() default DataSourceType.MASTER;
}
切面实现
通过 AOP 拦截注解,切换数据源:
package com.elvis.aspect;
import com.elvis.annotation.ReadWriteSplit;
import com.elvis.config.DataSourceContextHolder;
import com.elvis.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 读写分离切面(拦截注解,切换数据源)
*/
@Slf4j
@Aspect
@Component
public class ReadWriteSplitAspect implements Ordered {
/**
* 切入点:拦截所有带@ReadWriteSplit注解的方法
*/
@Pointcut("@annotation(com.elvis.annotation.ReadWriteSplit)")
public void readWriteSplitPoint() {
}
/**
* 环绕通知:切换数据源
*/
@Around("readWriteSplitPoint()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法上的注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ReadWriteSplit annotation = method.getAnnotation(ReadWriteSplit.class);
// 设置数据源类型
DataSourceType dataSourceType = annotation.value();
DataSourceContextHolder.setDataSourceType(dataSourceType);
log.info("切换数据源:{}", dataSourceType.name());
try {
// 执行方法
return joinPoint.proceed();
} finally {
// 清除数据源类型(避免线程复用导致问题)
DataSourceContextHolder.clearDataSourceType();
log.info("清除数据源类型");
}
}
/**
* 设置切面优先级(高于事务切面)
*/
@Override
public int getOrder() {
return 1;
}
}
实体类
对应 user_info 表:
package com.elvis.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user_info")
public class UserInfo {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//指定json格式
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
Mapper接口
package com.elvis.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.elvis.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
Service层
package com.elvis.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.elvis.entity.UserInfo;
public interface UserInfoService extends IService<UserInfo> {
/**
* 新增用户(写操作,主库)
*/
boolean addUser(UserInfo userInfo);
/**
* 查询用户(读操作,从库)
*/
UserInfo getUserById(Integer id);
}
实现类
package com.elvis.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.elvis.annotation.ReadWriteSplit;
import com.elvis.entity.UserInfo;
import com.elvis.enums.DataSourceType;
import com.elvis.mapper.UserInfoMapper;
import com.elvis.service.UserInfoService;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
/**
* 写操作:使用主库(默认,也可显式指定)
*/
@Override
@ReadWriteSplit(DataSourceType.MASTER)
public boolean addUser(UserInfo userInfo) {
return save(userInfo);
}
/**
* 读操作:使用从库
*/
@Override
@ReadWriteSplit(DataSourceType.SLAVE)
public UserInfo getUserById(Integer id) {
return getById(id);
}
}
Controller 层
package com.elvis.controller;
import com.elvis.entity.UserInfo;
import com.elvis.service.UserInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserInfoController {
private final UserInfoService userInfoService;
/**
* 新增用户(写操作→主库)
*/
@PostMapping("/add")
public String addUser(@RequestBody UserInfo userInfo) {
boolean success = userInfoService.addUser(userInfo);
return success ? "新增成功" : "新增失败";
}
/**
* 查询用户(读操作→从库)
*/
@GetMapping("/{id}")
public UserInfo getUser(@PathVariable Integer id) {
return userInfoService.getUserById(id);
}
}
启动类
package com.elvis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @Author XiangWei
* @Date 2026/4/1 14:17
* @Description: 启动类(排除默认数据源自动配置)
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) // 排除默认数据源配置
@MapperScan("com.elvis.mapper") // 扫描Mapper接口
@EnableAspectJAutoProxy(exposeProxy = true) // 开启AOP
public class MasterSlaveReplicationApplication {
public static void main(String[] args) {
// 启动Spring Boot应用//
SpringApplication.run(MasterSlaveReplicationApplication.class, args);
}
}
测试
利用postman、apifox等接口测试工具测试添加用户和查询接口,查看控制台运行日志
添加
2026-04-01T14:52:45.071+08:00 INFO 39900 --- [nio-8080-exec-6] com.elvis.aspect.ReadWriteSplitAspect : 切换数据源:MASTER
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b367182] was not registered for synchronization because synchronization is not active
2026-04-01T14:52:45.100+08:00 INFO 39900 --- [nio-8080-exec-6] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@325d26f0] will not be managed by Spring
==> Preparing: INSERT INTO user_info ( name ) VALUES ( ? )
==> Parameters: xiang(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b367182]
2026-04-01T14:52:45.121+08:00 INFO 39900 --- [nio-8080-exec-6] com.elvis.aspect.ReadWriteSplitAspect : 清除数据源类型
查询
2026-04-01T14:31:18.436+08:00 INFO 39900 --- [nio-8080-exec-4] com.elvis.aspect.ReadWriteSplitAspect : 切换数据源:SLAVE
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@783d5eba] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@112d95ea] will not be managed by Spring
==> Preparing: SELECT id,name,create_time FROM user_info WHERE id=?
==> Parameters: 3(Integer)
<== Columns: id, name, create_time
<== Row: 3, elvis, 2026-04-01 14:29:31
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@783d5eba]
2026-04-01T14:31:18.439+08:00 INFO 39900 --- [nio-8080-exec-4] com.elvis.aspect.ReadWriteSplitAspect : 清除数据源类型
更多推荐




所有评论(0)