先说结论1.2.0 之前只是对单库 MyBatis Druid 的封装。业务要现读写分离得引入wiki-datasource-jap模块来实现读写分析或者多数据源。这次改动的核心的地方用Master/Slave标注 Mapper 方法类上框架自动切换通过AOP切面拦截主从没标注的默认走主库。顺便把配置绑不上、Mapper 扫不到、RefreshScope早期访问报错这些BUG也修了。一、新增了什么1. 注解怎走主库、怎走从库注解作用Master强制走主库方法或类上均可Slave强制走从库方法或类上均可插入、修改等写操作或者强一致性读则标Master或不标默认就是主库。查询为主、能接受延迟的则标Slave。 以下是示例import com.cdkjframework.datasource.mybatis.annotation.Master; import com.cdkjframework.datasource.mybatis.annotation.Slave; /** * 默认主库可不加注解也行 */ public interface RmsUserMapper { RmsUser selectById(Long id); int updateById(RmsUser user); } /** * 只读查询走从库 */ public interface RmsUserQueryMapper { Slave ListRmsUser selectList(RmsUser query); } /** * 类上整体走从库个别写操作再强制回主库 */ Slave public interface RmsUserReportMapper { ListRmsUser selectReport(); Master int refreshSnapshot(); }2. 主要枚举、上下文与切面拦截核心类作用DataSourceType枚举MASTER/SLAVEDataSourceContextHolderThreadLocal存当前线程该用哪个库DynamicDataSource继承AbstractRoutingDataSource真正做路由DataSourceAspectAOP 拦截*..mapper..*切库并打日志要实现切换库规则很简单// DataSourceAspect 核心逻辑示意 DataSourceType dataSourceType; if (hasMasterAnnotation(method, targetClass, targetMethod)) { dataSourceType DataSourceType.MASTER; } else if (hasSlaveAnnotation(method, targetClass, targetMethod)) { dataSourceType DataSourceType.SLAVE; } else { // 未标注 Master / Slave 时默认走主库 dataSourceType DataSourceType.MASTER; }每次切换都会输出 INFO 日志排查「到底连接的是那个库」时很有用例如数据源切换: MASTER - com.framewiki.example.mapper.RmsUserMapper.updateById() 数据源切换: SLAVE - com.framewiki.example.mapper.RmsUserQueryMapper.selectList()3. 主从 Mapper 单独扫描类作用MapperPrimaryScannerConfiguration扫主库 Mapper 包绑primarySessionFactoryMapperScannerConfiguration扫从库 Mapper 包绑sessionFactoryMybatisMapperScanSupport从Environment用 Binder 解析配置避免启动早期读不到属性问题4. 测试补了DataSourceAspectTest、MybatisConfigBindingTest、MapperPrimaryScannerConfigurationTest、MapperScannerConfigurationTest涵盖了注解组合、配置绑定、扫描跳过/回退等场景的测试。二、优化了什么1. 优化了配置读不到 / 重复 Bean 问题因为以前MybatisConfig同时挂了Configuration和EnableConfigurationProperties。业务启动类如果再宽泛扫描com.cdkjframework容易注册出两个 Bean一个绑定配置一个则没有绑定配置。注入时都不知道拿到的是哪个。现在去掉了多余的Configuration只走EnableConfigurationProperties。2. Mapper 扫描时序BeanDefinitionRegistryPostProcessor执行得就很早了原来是直接注入MybatisConfig并调用getMaster()属性往往还没绑定完成再碰上RefreshScope可能直接No Scope registered for scope name refresh现在扫描类实现EnvironmentAware通过 Binder 读spring.datasource不依赖已创建完成的配置 Bean就避免了配置绑定过早的问题。// MybatisMapperScanSupport示意 static MybatisConfig bindMybatisConfig(Environment environment) { return Binder.get(environment) .bind(spring.datasource, Bindable.of(MybatisConfig.class)) .orElseGet(MybatisConfig::new); }3. 默认直接走主库现在只会读取Master/Slave注解没注解的一律走主库。4. 使用会话工厂更稳定primarySessionFactory标注PrimarysqlSessionTemplate明确Qualifier(primarySessionFactory)初始化失败直接抛异常避免悄悄返回 null三、调整了什么1. 配置结构调整支持 master / slaveMybatisConfig从扁平的单库支持修改成可嵌套的主从支持。公共 Mapper 包和 XML 仍可写在顶层主从共用。配置示例spring: datasource: # 公共扫描包 / XML主从均可回退到这里 mybatis-mapper: - com.framewiki.example.mapper mybatis-mapper-xml: - /mybatis/**/*.xml # Druid 连接池DataSourceConfig 读取建议配上 max-active: 20 initial-size: 5 min-idle: 5 filters: stat,wall # 主库配置 master: url: jdbc:mysql://10.10.0.33:3306/mobile?characterEncodingutf8serverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 从库配置 slave: url: jdbc:mysql://10.10.0.33:3306/mobile?characterEncodingutf8serverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver如果只配了master、没配slave从库会回退到主库连接地址就会以单库场景跑。2. 双数据源 双 SqlSessionFactory调整前调整后一个 Druid 数据源masterDataSourceslaveDataSourcedynamicDataSource一个 SqlSessionFactoryprimarySessionFactory主sessionFactory从3. 自动配置装配方式MybatisAutoConfiguration里Mapper 扫描从Import改为静态Bean注册方式便于配合EnvironmentAware。4. MySQL 驱动调整为 test scopepom.xml里mysql-connector-j只在测试依赖中引入避免把 GPL 协议带进业务发行中。实际业务项目必须自己引入驱动否则会出现ClassNotFoundException: com.mysql.cj.jdbc.Driver需要实际业务项目自行引入驱动:!-- 业务项目 pom.xml -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency四、怎么接入完整示例1. 依赖dependency groupIdcom.framewiki/groupId artifactIdwiki-datasource/artifactId version1.2.0/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency2. 启动类import com.cdkjframework.datasource.mybatis.annotation.EnableAutoMybatis; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.SpringApplication; EnableAutoMybatis SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }若使用wiki-all的EnableAutoWiki内部已包含了EnableAutoMybatis则不必再单独添加。3. Mapper 与 Service// mapper/RmsUserMapper.java —— 写操作默认走主库 public interface RmsUserMapper { int modifyRmsUser(RmsUserEntity entity); RmsUserEntity selectById(Long id); } // mapper/RmsUserQueryMapper.java —— 列表查询走从库 import com.cdkjframework.datasource.mybatis.annotation.Slave; public interface RmsUserQueryMapper { Slave ListRmsUserEntity selectList(RmsUserEntity query); }/** * 服务实现类 */ Service RequiredArgsConstructor public class RmsUserServiceImpl implements RmsUserService { /** * 写操作走主库 */ private final RmsUserMapper rmsUserMapper; /** * 查询操作走从库 */ private final RmsUserQueryMapper rmsUserQueryMapper; /** * 写操作走主库 */ Transactional(rollbackFor Exception.class) public void modifyRmsUser(RmsUserEntity entity) { // 走主库 rmsUserMapper.modifyRmsUser(entity); } /** * 查询操作走从库 */ public ListRmsUserEntity listUsers(RmsUserEntity query) { // 走从库方法上有 Slave return rmsUserQueryMapper.selectList(query); } }4. 怎么确认切换正确的主从库启动后调一次写、一次读日志里应能看到正确的切换日志数据源切换: MASTER - ...RmsUserMapper.modifyRmsUser() 数据源切换: SLAVE - ...RmsUserQueryMapper.selectList()如果日志太多时可单独调整级别logging: level: com.cdkjframework.datasource.mybatis.aspect.DataSourceAspect: INFO五、小结wiki-datasource在 1.2.0 的变化可以概括为以下方面新增Master/Slave注解、主从扫描、切换日志输出优化配置绑定、扫描时序、默认主库规则、会话工厂稳定性调整YAML 支持 master/slave、双数据源双工厂、驱动下沉业务方接入EnableAutoMybatis 配置主从注解 Mapper 按需标注注解 自行引入 MySQL 驱动需要读写分离需求时优先用注解标清楚没有从库时默认主库也能正常工作。
维基框架(Wiki-Framework) 1.2.0:Mybatis 升级为主从读写分离
发布时间:2026/7/17 17:32:30
先说结论1.2.0 之前只是对单库 MyBatis Druid 的封装。业务要现读写分离得引入wiki-datasource-jap模块来实现读写分析或者多数据源。这次改动的核心的地方用Master/Slave标注 Mapper 方法类上框架自动切换通过AOP切面拦截主从没标注的默认走主库。顺便把配置绑不上、Mapper 扫不到、RefreshScope早期访问报错这些BUG也修了。一、新增了什么1. 注解怎走主库、怎走从库注解作用Master强制走主库方法或类上均可Slave强制走从库方法或类上均可插入、修改等写操作或者强一致性读则标Master或不标默认就是主库。查询为主、能接受延迟的则标Slave。 以下是示例import com.cdkjframework.datasource.mybatis.annotation.Master; import com.cdkjframework.datasource.mybatis.annotation.Slave; /** * 默认主库可不加注解也行 */ public interface RmsUserMapper { RmsUser selectById(Long id); int updateById(RmsUser user); } /** * 只读查询走从库 */ public interface RmsUserQueryMapper { Slave ListRmsUser selectList(RmsUser query); } /** * 类上整体走从库个别写操作再强制回主库 */ Slave public interface RmsUserReportMapper { ListRmsUser selectReport(); Master int refreshSnapshot(); }2. 主要枚举、上下文与切面拦截核心类作用DataSourceType枚举MASTER/SLAVEDataSourceContextHolderThreadLocal存当前线程该用哪个库DynamicDataSource继承AbstractRoutingDataSource真正做路由DataSourceAspectAOP 拦截*..mapper..*切库并打日志要实现切换库规则很简单// DataSourceAspect 核心逻辑示意 DataSourceType dataSourceType; if (hasMasterAnnotation(method, targetClass, targetMethod)) { dataSourceType DataSourceType.MASTER; } else if (hasSlaveAnnotation(method, targetClass, targetMethod)) { dataSourceType DataSourceType.SLAVE; } else { // 未标注 Master / Slave 时默认走主库 dataSourceType DataSourceType.MASTER; }每次切换都会输出 INFO 日志排查「到底连接的是那个库」时很有用例如数据源切换: MASTER - com.framewiki.example.mapper.RmsUserMapper.updateById() 数据源切换: SLAVE - com.framewiki.example.mapper.RmsUserQueryMapper.selectList()3. 主从 Mapper 单独扫描类作用MapperPrimaryScannerConfiguration扫主库 Mapper 包绑primarySessionFactoryMapperScannerConfiguration扫从库 Mapper 包绑sessionFactoryMybatisMapperScanSupport从Environment用 Binder 解析配置避免启动早期读不到属性问题4. 测试补了DataSourceAspectTest、MybatisConfigBindingTest、MapperPrimaryScannerConfigurationTest、MapperScannerConfigurationTest涵盖了注解组合、配置绑定、扫描跳过/回退等场景的测试。二、优化了什么1. 优化了配置读不到 / 重复 Bean 问题因为以前MybatisConfig同时挂了Configuration和EnableConfigurationProperties。业务启动类如果再宽泛扫描com.cdkjframework容易注册出两个 Bean一个绑定配置一个则没有绑定配置。注入时都不知道拿到的是哪个。现在去掉了多余的Configuration只走EnableConfigurationProperties。2. Mapper 扫描时序BeanDefinitionRegistryPostProcessor执行得就很早了原来是直接注入MybatisConfig并调用getMaster()属性往往还没绑定完成再碰上RefreshScope可能直接No Scope registered for scope name refresh现在扫描类实现EnvironmentAware通过 Binder 读spring.datasource不依赖已创建完成的配置 Bean就避免了配置绑定过早的问题。// MybatisMapperScanSupport示意 static MybatisConfig bindMybatisConfig(Environment environment) { return Binder.get(environment) .bind(spring.datasource, Bindable.of(MybatisConfig.class)) .orElseGet(MybatisConfig::new); }3. 默认直接走主库现在只会读取Master/Slave注解没注解的一律走主库。4. 使用会话工厂更稳定primarySessionFactory标注PrimarysqlSessionTemplate明确Qualifier(primarySessionFactory)初始化失败直接抛异常避免悄悄返回 null三、调整了什么1. 配置结构调整支持 master / slaveMybatisConfig从扁平的单库支持修改成可嵌套的主从支持。公共 Mapper 包和 XML 仍可写在顶层主从共用。配置示例spring: datasource: # 公共扫描包 / XML主从均可回退到这里 mybatis-mapper: - com.framewiki.example.mapper mybatis-mapper-xml: - /mybatis/**/*.xml # Druid 连接池DataSourceConfig 读取建议配上 max-active: 20 initial-size: 5 min-idle: 5 filters: stat,wall # 主库配置 master: url: jdbc:mysql://10.10.0.33:3306/mobile?characterEncodingutf8serverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 从库配置 slave: url: jdbc:mysql://10.10.0.33:3306/mobile?characterEncodingutf8serverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver如果只配了master、没配slave从库会回退到主库连接地址就会以单库场景跑。2. 双数据源 双 SqlSessionFactory调整前调整后一个 Druid 数据源masterDataSourceslaveDataSourcedynamicDataSource一个 SqlSessionFactoryprimarySessionFactory主sessionFactory从3. 自动配置装配方式MybatisAutoConfiguration里Mapper 扫描从Import改为静态Bean注册方式便于配合EnvironmentAware。4. MySQL 驱动调整为 test scopepom.xml里mysql-connector-j只在测试依赖中引入避免把 GPL 协议带进业务发行中。实际业务项目必须自己引入驱动否则会出现ClassNotFoundException: com.mysql.cj.jdbc.Driver需要实际业务项目自行引入驱动:!-- 业务项目 pom.xml -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency四、怎么接入完整示例1. 依赖dependency groupIdcom.framewiki/groupId artifactIdwiki-datasource/artifactId version1.2.0/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency2. 启动类import com.cdkjframework.datasource.mybatis.annotation.EnableAutoMybatis; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.SpringApplication; EnableAutoMybatis SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }若使用wiki-all的EnableAutoWiki内部已包含了EnableAutoMybatis则不必再单独添加。3. Mapper 与 Service// mapper/RmsUserMapper.java —— 写操作默认走主库 public interface RmsUserMapper { int modifyRmsUser(RmsUserEntity entity); RmsUserEntity selectById(Long id); } // mapper/RmsUserQueryMapper.java —— 列表查询走从库 import com.cdkjframework.datasource.mybatis.annotation.Slave; public interface RmsUserQueryMapper { Slave ListRmsUserEntity selectList(RmsUserEntity query); }/** * 服务实现类 */ Service RequiredArgsConstructor public class RmsUserServiceImpl implements RmsUserService { /** * 写操作走主库 */ private final RmsUserMapper rmsUserMapper; /** * 查询操作走从库 */ private final RmsUserQueryMapper rmsUserQueryMapper; /** * 写操作走主库 */ Transactional(rollbackFor Exception.class) public void modifyRmsUser(RmsUserEntity entity) { // 走主库 rmsUserMapper.modifyRmsUser(entity); } /** * 查询操作走从库 */ public ListRmsUserEntity listUsers(RmsUserEntity query) { // 走从库方法上有 Slave return rmsUserQueryMapper.selectList(query); } }4. 怎么确认切换正确的主从库启动后调一次写、一次读日志里应能看到正确的切换日志数据源切换: MASTER - ...RmsUserMapper.modifyRmsUser() 数据源切换: SLAVE - ...RmsUserQueryMapper.selectList()如果日志太多时可单独调整级别logging: level: com.cdkjframework.datasource.mybatis.aspect.DataSourceAspect: INFO五、小结wiki-datasource在 1.2.0 的变化可以概括为以下方面新增Master/Slave注解、主从扫描、切换日志输出优化配置绑定、扫描时序、默认主库规则、会话工厂稳定性调整YAML 支持 master/slave、双数据源双工厂、驱动下沉业务方接入EnableAutoMybatis 配置主从注解 Mapper 按需标注注解 自行引入 MySQL 驱动需要读写分离需求时优先用注解标清楚没有从库时默认主库也能正常工作。