SpringBoot+MybatisPlus配置读写分离
1.概述
继承AbstractRoutingDataSource接口实现读写分离配置。使用的主要技术如下:
- SpringBoot 2.1.12.RELEASE
- MybatisPlus
- alibaba.druid数据库连接池
- mysql数据库
- SpringAop
2.配置文件
mybatis-plus:
# 如果是放在src/main/java目录下 classpath:/com/yourpackage/*/mapper/*Mapper.xml
# 如果是放在resource目录 classpath:/mapper/*Mapper.xml
mapper-locations: classpath:com/bbdog/dao/xml/*Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.bbdog.dao.model
global-config:
#主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
id-type: 0
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
field-strategy: 1
#刷新mapper 调试神器
refresh-mapper: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
#配置JdbcTypeForNull
jdbc-type-for-null: 'null'
spring:
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${spring.virtualIp}:3306/bbdog
username: master
password: ******
#----数据库连接池配置----------------------
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 1
maxActive: 50
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: false
#maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties:
druid:
stat:
mergeSql: true
slowSqlMillis: 5000
# 合并多个DruidDataSource的监控数据
#useGlobalDataSourceStat: true
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://${spring.virtualIp}:3306/bbdog
username: slave
password: ******
#----数据库连接池配置----------------------
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 1
maxActive: 50
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: false
#maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties:
druid:
stat:
mergeSql: true
slowSqlMillis: 5000
# 合并多个DruidDataSource的监控数据
#useGlobalDataSourceStat: true
3.SpringBoot启动类设置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) // 设置动态数据源需要,禁用数据源自动配置
@EnableTransactionManagement//开启springBoot事务
@MapperScan("com.bbdog.dao.mapper*")
@EnableCaching//开启基于注解的缓存
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
4.创建数据源类型
public enum SourceName {
read("read"), write("write");
private String value;
SourceName(String value) {
this.value = value;
}
public String value() {
return this.value;
}
}
5.构建切换数据源类
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 将 read 数据源的 key作为默认数据源的 key
*/
@Override
protected String initialValue() {
return SourceName.read.value();
}
};
/**
* 数据源的 key集合,用于切换时判断数据源是否存在
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* 切换数据源
*
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 获取数据源
*
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 重置数据源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
/**
* 判断是否包含数据源
*
* @param key 数据源key
* @return
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
/**
* 添加数据源keys
*
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
return dataSourceKeys.addAll(keys);
}
}
6.继承AbstractRoutingDataSource接口实现动态数据源
public class AutoChooseDataSource extends AbstractRoutingDataSource {
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
/**
* 设置默认数据源
*
* @param defaultDataSource
*/
@Override
public void setDefaultTargetDataSource(Object defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
}
/**
* 设置数据源
*
* @param dataSources
*/
@Override
public void setTargetDataSources(Map<Object, Object> dataSources) {
super.setTargetDataSources(dataSources);
// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
}
7.数据源配置类设置
参照自动配置类MybatisPlusAutoConfiguration.java中的SqlSessionFactory配置来为添加自己的动态数据源
@SuppressWarnings("ConstantConditions")
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class})
@ConditionalOnBean(DataSource.class)//容器中有DataSource类就可以调用该配置类的方法了
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration {
...
/*
将自己配置的动态数据源放入容器中,容器会自动注入到该方法的入参。
由于容器中有多个DataSource类,所以要将自己的动态数据源设置为默认@Primary
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
....
return factory.getObject();
}
}
数据源配置类内容:
@Configuration
public class DruidConfiguration {
/**
* 动态数据源配置**********************************↓↓↓↓↓↓↓↓↓↓↓↓↓↓
***************************/
@Bean(name = "write", destroyMethod = "close", initMethod = "init")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource master() {
return druidDataSource();
}
@Bean(name = "read", destroyMethod = "close", initMethod = "init")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slave() {
return druidDataSource();
}
@Bean("dataSource")
@Primary//自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
public DataSource autoChooseDataSource() {
AutoChooseDataSource autoChooseDataSource = new AutoChooseDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(SourceName.write.value(), master());
dataSourceMap.put(SourceName.read.value(), slave());
// 将 read 数据源作为默认指定的数据源
autoChooseDataSource.setDefaultTargetDataSource(slave());
// 将 read 和 write 数据源作为指定的数据源
autoChooseDataSource.setTargetDataSources(dataSourceMap);
return autoChooseDataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
// 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
return new DataSourceTransactionManager(autoChooseDataSource());
}
/**
* 动态数据源配置**********************************↑↑↑↑↑↑↑↑↑↑↑↑↑↑
***************************/
public DataSource druidDataSource() {
return new DruidDataSource();
}
}
8.创建数据源切换注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 数据源key值
* @return
*/
SourceName value();
}
9.创建数据源切换切面
@Aspect
@Order(-1) // 该切面应当先于 @Transactional 执行
@Component
public class DynamicDataSourceAspect {
private static Logger _log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
/**
* 切换数据源
*
* @param point
* @param dataSource
*/
@Before("@annotation(dataSource))")
public void switchDataSource(JoinPoint point, DataSource dataSource) {
if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource.value().name())) {
_log.error("DataSource [{}] 不存在,使用默认 DataSource [{}] ",
dataSource.value(),
DynamicDataSourceContextHolder.getDataSourceKey());
} else {
// 切换数据源
DynamicDataSourceContextHolder.setDataSourceKey(dataSource.value().name());
_log.debug("切换 DataSource 至 [{}] ,引起切换方法是 [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(),
point.getSignature());
}
}
/**
* 重置数据源
*
* @param point
* @param dataSource
*/
@After("@annotation(dataSource))")
public void restoreDataSource(JoinPoint point, DataSource dataSource) {
// 将数据源置为默认数据源
DynamicDataSourceContextHolder.clearDataSourceKey();
_log.debug("重置 DataSource 至 [{}] ,引起重置的方法是 [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(),
point.getSignature());
}
}
10.示例
/**
* 删除角色
*
* @param role
* @return
*/
@Override
@DataSource(SourceName.write)
@Transactional
public R deleteRoleById(Role role) {
role.setUpdateTime(Utils.getCurrentFormatDateStr());
role.setValid("0");
Integer delete = baseMapper.updateById(role);
this.clearPermissionsInRoleId(role.getRoleId());
return new R(1 == delete);
}
分类:
Spring框架
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器