SpringBoot 的多数据源配置与动态切换
Posted 潇潇雨歇_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot 的多数据源配置与动态切换相关的知识,希望对你有一定的参考价值。
在使用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或引入其他数据库,因此需要配置多个数据源,并能实现其动态切换。
一.工程目录截图
二.相关代码
2.1 多数据源application.yml配置文件
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 从库数据源popedom
slave:
# 从数据源开关/默认关闭
enabled: true
url: jdbc:mysql://localhost:3306/test_slave_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: vrmp
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
2.2 自定义注解DataSource
package com.aliyun.datasource.common.annotation;
import com.aliyun.datasource.common.enums.DataSourceType;
import java.lang.annotation.*;
/**
* 自定义多数据源切换注解
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
* @author haige
*/
@Target( ElementType.METHOD, ElementType.TYPE )
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
2.3 主从库枚举定义
package com.aliyun.datasource.common.enums;
/**
* 数据源
*
* @author haige
*/
public enum DataSourceType
/**
* 主库
*/
MASTER("master"),
/**
* 从库
*/
SLAVE("slave");
private String value;
DataSourceType(String value)
this.value = value;
public String getValue()
return value;
2.4 数据源的切换处理
package com.aliyun.datasource.common.holder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
*
* @author haige
*/
public class DynamicDataSourceContextHolder
public static final Logger log = LoggerFactory.getLogger(com.aliyun.datasource.common.holder.DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType)
log.info("切换到数据源", dsType);
CONTEXT_HOLDER.set(dsType);
/**
* 获得数据源的变量
*/
public static String getDataSourceType()
return CONTEXT_HOLDER.get();
/**
* 清空数据源变量
*/
public static void clearDataSourceType()
CONTEXT_HOLDER.remove();
2.5 数据源注解的AOP拦截处理
package com.aliyun.datasource.common.aspectj;
import com.aliyun.datasource.common.annotation.DataSource;
import com.aliyun.datasource.common.holder.DynamicDataSourceContextHolder;
import com.aliyun.datasource.common.util.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 多数据源处理
*
* @author haige
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.aliyun.datasource.common.annotation.DataSource)"
+ "|| @within(com.aliyun.datasource.common.annotation.DataSource)")
public void dsPointCut()
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource))
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
try
return point.proceed();
finally
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point)
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource))
return dataSource;
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
2.6 数据源bean的配置注入
package com.aliyun.datasource.common.configuration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.aliyun.datasource.common.enums.DataSourceType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.annotation.Profile;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan("com.aliyun.datasource.test.mapper*")
public class MyBatisPlusConfiguration
/*
* 分页插件,自动识别数据库类型
* 多租户,请参考官网【插件扩展】
*/
@Bean
public PaginationInterceptor paginationInterceptor()
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 开启 PageHelper 的支持
// paginationInterceptor.setL(true);
return paginationInterceptor;
/**
* SQL执行效率插件
*/
@Bean
@Profile("local","dev")// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor()
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1000);
performanceInterceptor.setFormat(true);
return performanceInterceptor;
@Bean(name = "master")
@ConfigurationProperties(prefix = "spring.datasource.druid.master" )
public DataSource master()
return DruidDataSourceBuilder.create().build();
@Bean(name = "slave")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave" )
public DataSource slave()
return DruidDataSourceBuilder.create().build();
/**
* 动态数据源配置
* @return
*/
@Bean
@Primary
public DataSource multipleDataSource(@Qualifier("master") DataSource master, @Qualifier("slave") DataSource slave)
MultipleDataSource multipleDataSource = new MultipleDataSource();
Map< Object, Object > targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.getValue(), master);
targetDataSources.put(DataSourceType.SLAVE.getValue(), slave);
//添加数据源
multipleDataSource.setTargetDataSources(targetDataSources);
//设置默认数据源
multipleDataSource.setDefaultTargetDataSource(master);
return multipleDataSource;
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(multipleDataSource(master(),slave()));
//sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*/*Mapper.xml"));
MybatisConfiguration configuration = new MybatisConfiguration();
//configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setPlugins(new Interceptor[]
paginationInterceptor() //添加分页功能
);
//sqlSessionFactory.setGlobalConfig(globalConfiguration());
return sqlSessionFactory.getObject();
2.7 数据源切换测试
package com.aliyun.datasource.test.controller;
import com.aliyun.datasource.common.util.Result;
import com.aliyun.datasource.common.util.StatusCode;
import com.aliyun.datasource.test.entity.VideoRoom;
import com.aliyun.datasource.test.service.IVideoRoomService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 前端控制器
* @author haige
* @since 2021-11-24
*/
@Slf4j
@RestController
@RequestMapping("/videoRoom")
public class VideoRoomController
@Autowired
public IVideoRoomService videoRoomService;
/**
* 测试多数据源动态切换
*/
@GetMapping("/testDatasources")
public Result testDataSource()
List<VideoRoom> roomList = new ArrayList<>();
roomList.add(videoRoomService.testMasterData());
roomList.add(videoRoomService.testSlaveData());
roomList.add(videoRoomService.testMasterData());
return new Result(StatusCode.SUCCESS,"查询成功",roomList);
@Service
public class VideoRoomServiceImpl extends ServiceImpl<VideoRoomMapper, VideoRoom> implements IVideoRoomService
@Override
// @DataSource(DataSourceType.MASTER) 不用注解则默认使用MASTER数据源
public VideoRoom testMasterData()
return this.baseMapper.selectById(1);
@Override
@DataSource(DataSourceType.SLAVE)
public VideoRoom testSlaveData()
return this.baseMapper.selectById(5);
三.测试结果
四.工程完整代码
以上是关于SpringBoot 的多数据源配置与动态切换的主要内容,如果未能解决你的问题,请参考以下文章