先说结论
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
List<RmsUser> selectList(RmsUser query);
}
/**
* 类上整体走从库,个别写操作再强制回主库
*/
@Slave
public interface RmsUserReportMapper {
List<RmsUser> selectReport();
@Master
int refreshSnapshot();
}
2. 主要枚举、上下文与切面拦截(核心)
|
类
|
作用
|
| --- | --- |
|
DataSourceType
|
枚举:MASTER / SLAVE
|
|
DataSourceContextHolder
|
ThreadLocal,存当前线程该用哪个库
|
|
DynamicDataSource
|
继承 AbstractRoutingDataSource,真正做路由
|
|
DataSourceAspect
|
AOP 拦截 *..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 包,绑 primarySessionFactory
|
|
MapperScannerConfiguration
|
扫从库 Mapper 包,绑 sessionFactory
|
|
MybatisMapperScanSupport
|
从 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标注@Primary -
sqlSessionTemplate明确@Qualifier("primarySessionFactory") -
初始化失败直接抛异常,避免悄悄返回 null
三、调整了什么?
1. 配置结构调整:支持 master / slave
MybatisConfig 从扁平的单库支持,修改成可嵌套的主从支持。公共 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?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库配置
slave:
url: jdbc:mysql://10.10.0.33:3306/mobile?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
如果只配了 master、没配 slave,从库会回退到主库连接地址,就会以单库场景跑。
2. 双数据源 + 双 SqlSessionFactory
|
调整前
|
调整后
|
| --- | --- |
|
一个 Druid 数据源
|
masterDataSource + slaveDataSource + dynamicDataSource
|
|
一个 SqlSessionFactory
|
primarySessionFactory(主)+ sessionFactory(从)
|
3. 自动配置装配方式
MybatisAutoConfiguration 里,Mapper 扫描从 @Import 改为静态 @Bean 注册方式便于配合 EnvironmentAware。
4. MySQL 驱动调整为 test scope
pom.xml 里 mysql-connector-j 只在测试依赖中引入,避免把 GPL 协议带进业务发行中。
实际业务项目必须自己引入驱动,否则会出现:
ClassNotFoundException: com.mysql.cj.jdbc.Driver
需要实际业务项目自行引入驱动:
<!-- 业务项目 pom.xml -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
四、怎么接入(完整示例)?
1. 依赖
<dependency>
<groupId>com.framewiki</groupId>
<artifactId>wiki-datasource</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
2. 启动类
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
List<RmsUserEntity> 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 List<RmsUserEntity> 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 驱动
需要读写分离需求时,优先用注解标清楚;没有从库时,默认主库也能正常工作。