本文简单的介绍一下基于SpringBoot框架动态多数据源切换的实现,采用主从配置的方式,配置master、slave两个数据库。

一、配置主从数据库

spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/practice?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: true
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456

二、创建数据源枚举类

public enum DataSourceType {
/**
* 主库
*/
MASTER, /**
* 从库
*/
SLAVE
}

三、数据源切换处理

  创建一个数据源切换处理类,有对数据源变量的获取、设置和清空的方法。其中的ThreadLocal用于保存某个线程共享变量。详细的ThreadLocal的相关了解,可以查看地址:https://www.cnblogs.com/slivelove/p/10950527.html

public class DynamicDataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /**
* 设置数据源变量
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType){
log.info("切换到{}数据源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
} /**
* 获取数据源变量
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
} /**
* 清空数据源变量
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}

四、继承AbstractRoutingDataSource

  动态切换数据源主要依靠AbstractRoutingDataSource。创建一个AbstractRoutingDataSource的子类,重写determineCurrentLookupKey方法,用于决定使用哪一个数据源。这里主要用到AbstractRoutingDataSource的两个属性defaultTargetDataSource和targetDataSources。defaultTargetDataSource默认目标数据源,targetDataSources(map类型)存放用来切换的数据源。

public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
} /**
* 根据Key获取数据源的信息
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}

五、注入数据源

@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
} @Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}

六、自定义多数据源切换注解

  设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 切换数据源名称
*/
DataSourceType value() default DataSourceType.MASTER;
}

七、AOP拦截类的实现

  通过拦截上面的注解,在其执行之前处理设置当前执行SQL的数据源的信息,CONTEXT_HOLDER.set(dataSourceType)这里的数据源信息从我们设置的注解上面获取信息,如果没有设置就是用默认的数据源的信息。

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
private Logger log = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.wlfu.common.annotation.DataSource)")
public void dsPointCut() { } @Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}

  这里使用@Around,在调用目标方法前,进行aop拦截,通过解析注解上的值来切换数据源。在调用方法结束后,清除数据源。也可以使用@Before和@After来编写,原理一样,这里就不多说了。

八、使用切换数据源注解

  设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上。

@DataSource(value = DataSourceType.SLAVE)
@PostMapping("/list")
@ResponseBody
public TableDataInfo list() { }

最新文章

  1. ****CodeIgniter使用cli模式运行,把php作为shell编程
  2. linux内核中异步通信机制--信号处理机制【转】
  3. 30. Distinct Subsequences
  4. C#串口操作类,包括串口读写操作
  5. 【C++基础】 类中static private public protected
  6. linux网络编程笔记——TCP
  7. UNION ALL
  8. Android动态加载jar、apk的实现
  9. Dom4j下载及使用Dom4j读写XML简介
  10. 【锋利的Jquery】读书笔记七
  11. 这是一名Java学者关于学习方向的建议
  12. socket模型处理多个客户端
  13. Android开发艺术探究Note
  14. 物料分类新增&amp;更新
  15. zepto中的scrollTo
  16. pycharm中出现的错误
  17. Hibernate 基础入门
  18. Java基础知识(JAVA基本数据类型包装类)
  19. ActiveReports 报表控件V12新特性 -- 新增JSON和CSV导出
  20. POJ 1182 食物链(并查集的使用)

热门文章

  1. Spark学习之路 (二十三)SparkStreaming的官方文档[转]
  2. ElementUI的Table表格添加自定义头CheckBox多选框
  3. idea插件不兼容问题
  4. CentOS 7 部署 Redis(单机版)
  5. leetcode四道组合总和问题总结(39+40+216+377)
  6. Wannafly Winter Camp 2020 Day 5G Cryptographically Secure Pseudorandom Number Generator - 分块
  7. WPF实现高仿统计标题卡
  8. 小白月赛22 B : 树上子链
  9. 【剑指Offer】44、翻转单词顺序列
  10. SpringData JPA快速入门和基本的CRUD操作以及Specifications条件查询