SpringBoot中如何通过AOP实现多数据源动态切换?
本文介绍了SpringBoot中,通过AOP实现多数据源动态切换的方法。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
7. 继承AbstractRoutingDataSource定义数据源标识
前言
在当今的软件开发领域,许多应用需要访问多个数据源,以下是多数据源的一些重要使用场景:
1. 读写分离:在高流量应用中,为了提高性能和负载均衡,通常会采用读写分离策略。读操作和写操作被路由到不同的数据库,以减轻数据库服务器的压力。多数据源是实现这一策略的关键,它能够选择适当的数据源,以实现读写分离。
2. 数据迁移和同步:在数据迁移或数据同步操作中,需要同时连接到源数据库和目标数据库。多数据源可确保数据从一个源复制到另一个目标,或者实现不同数据库之间的数据同步。
3. 业务和技术隔离:有时一个应用程序需要同时访问多个数据库,每个数据库可能用于不同的业务或技术目的。多数据源可确保请求根据业务或技术要求路由到正确的数据源。
综上所述,多数据源在各种关键应用场景中发挥着重要作用,有助于确保数据的隔离、性能和安全性。在接下来的部分,我们将探讨Spring Boot中来实现多数据源,以满足这些复杂需求的方法。
一、Springboot中实现多数据源的方法?
多数据源设置可以分为静态数据源切换和动态数据源切换两种方式:
1. 静态数据源切换
通常情况下,我们会为每个数据源配置独立的sessionFactory和DAO层代码(以Hibernate或MyBatis为例)。这种方式被称为静态数据源配置。
2. 动态数据源切换(基于AOP)
在动态数据源切换中,我们引入了面向切面编程(AOP)的概念。通过AOP,我们可以在运行时动态切换数据源,而不需要在代码中硬编码多个SessionFactory。这种基于AOP的动态数据源切换方式更加灵活、可维护,使系统更具扩展性。
二、使用AOP实现多数据源动态切换
1.引入依赖
我们使用druid数据库连接池,在pom.xml文件中引入druid的坐标:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
2.在application.yml中配置多数据源
配置如下:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master: #master数据源
url: jdbc:mysql://localhost:3306/hrs?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
slave: #slave数据源
url: jdbc:mysql://localhost:3306/hrs_slave?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
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
statViewServlet:
enabled: true
url-pattern: /monitor/druid
3. 数据源属性配置类
@Configuration
public class DruidProperties {
@Value("${spring.datasource.druid.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
public DruidDataSource dataSource(DruidDataSource datasource) {
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}
4. 数据源常量类
public interface DataSourceName {
String DEFAULT_DATASOURCE_NAME="master";
String OTHER_DATASOURCE_NAME="slave";
}
5. 自定义多数据源配置类
@Configuration
public class DruidConfig
{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource)
{
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceName.DEFAULT_DATASOURCE_NAME, masterDataSource);
targetDataSources.put(DataSourceName.OTHER_DATASOURCE_NAME, slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
6. 利用ThreadLocal解决线程安全问题
public class DynamicDataSourceContextHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDateSoureName(String dsName) {
LOGGER.info("切换到{}数据源", dsName);
CONTEXT_HOLDER.set(dsName);
}
/**
* 获得数据源的变量
*/
public static String getDateSoureName() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void removeDataSourceName() {
CONTEXT_HOLDER.remove();
}
}
7. 继承AbstractRoutingDataSource定义数据源标识
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDateSoureName();
}
}
8. 定义多数据源注解DataSource
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})//作用在类和方法上
// @Inherited//当以后我们在定义一个作用于类的注解时候,如果希望该注解也作用于其子类,那么可以用@Inherited 来进行修饰。
public @interface DataSource {
String value() default DataSourceName.DEFAULT_DATASOURCE_NAME;
}
9. 定义DataSource注解切片
在transaction interpter执行之前就把动态数据源配置好,所以在动态数据源的配置的AOP切片上加入Order(1),让其先执行即可。
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class);
//定义切点,拦截@DataSource标注的类和方法
@Pointcut("@annotation(com.example.demo.annotation.DataSource)")
public void pointCut(){}
@Around("pointCut()")
public Object useDataSource(ProceedingJoinPoint pjp) throws Throwable{
// 获取注解中数据源名称
// 1.获取当前方法
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 2.获取当前方法上@DataSource的值,若方法上不存在,查找类上的值
Method method = methodSignature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
// 3.若注解不为空,获取值,并存入本地线程
if(dataSource!=null){
String value = dataSource.value();
//将值存入ThreadLocal中
LOGGER.info("切换数据源为{}", dataSource.value());
DynamicDataSourceContextHolder.setDateSoureName(value);
}
//4.执行原方法,有返回值直接返回
try {
return pjp.proceed();
} finally {
if(dataSource!=null) {
// 销毁数据源 在执行方法之后
LOGGER.info("销毁数据源{}", dataSource.value());
//5.移除本地线程的值
DynamicDataSourceContextHolder.removeDataSourceName();
}
}
}
}
10. 在调用方法上增加注解
我们需要在方法上添加@DataSource("数据源名称")注解,这样就可以利用AOP实现动态切换了。
可以在service层方法上增加注解:
@Service
public class TbDeptServiceImpl implements TbDeptService {
。。。
@DataSource(value = DataSourceName.OTHER_DATASOURCE_NAME)
@Override
public PageVO<TbDept> pageListSlave(TbDeptQuery query) {
List<TbDept> pageList = tbDeptMapper.pageList(query);
int totalCount = tbDeptMapper.pageListCount(query);
// result
PageVO<TbDept> result = new PageVO<>(pageList, query.getPage(), query.getSize(), totalCount);
return result;
}
。。。
}
也可以在mapper层方法上增加注解:
@Mapper
@Repository
public interface TbDeptMapper {
。。。
@DataSource(value = DataSourceName.OTHER_DATASOURCE_NAME)
TbDept load(int id);
。。。
}
总结
本文介绍了SpringBoot中,通过AOP实现多数据源动态切换的方法。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)