mysql多数据源切换_Spring 多数据源动态切换实现与原理分析

本文介绍了在Spring Boot中实现多数据源动态切换的详细步骤,包括配置各数据源、创建动态数据源以及使用AOP进行数据源切换。通过DynamicDataSource和AbstractRoutingDataSource结合ThreadLocal来实现不同数据源的切换,确保多线程环境下数据源的正确选择。同时,文章还解析了动态数据源切换的原理,强调了在不使用时清理ThreadLocal以防止内存泄漏的重要性。
摘要由CSDN通过智能技术生成

一 多数据源

在平常的开发过程中,我们经常会遇到对多个数据库进行读写的场景,比如说数据库主从读写分离啊,不同数据库数据读写啊,等等。

二 Srping Boot 多数据源动态切换实战

实现多数据源动态切换主要包含以下几步:

各数据源dataSource注入配置

动态数据源配置

AOP切面注解实现动态切换

下面就按照三个步骤来实现多数据源的动态切换了

1.各数据源配置注入

这一步就很平常了,就跟配置一个数据源是一样的,具体就不多说了,附上代码:

yml配置

# 多数据源Mysql配置

docker:

mysql:

driver-class-name: com.mysql.jdbc.Driver

jdbc-url: jdbc:mysql://localhost:3307/dynelm?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC

username: root

password: root

config配置

/**

* 动态源数据

* @return

* @throws SQLException

*/

@Bean(name = "dockerMysql")

@ConfigurationProperties(prefix = "docker.mysql")

public DataSource dockerMysqlSource(){

return DataSourceBuilder.create().build();

}

2.动态数据源(DynamicDataSource)配置

spring.jdbc 主要是通过AbstractRoutingDataSource 来控制数据源,所以DynamicDataSource主要是通过继承AbstractRoutingDataSource来变换绑定的数据源来实现的,具体的实现如下:

public class DynamicDataSource extends AbstractRoutingDataSource {

private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

/**

* 使用线程的ThreadLocal 保证多线程的数据源正确

*/

private static final ThreadLocal DATA_SOURCE_KEY = new ThreadLocal<>();

public static void changeDataSource(String dataSourceKey) {

DATA_SOURCE_KEY.set(dataSourceKey);

}

/**

* 需要防止 内存溢出

*/

public static void clearDataSource() {

DATA_SOURCE_KEY.remove();

}

/**

* <>

* 这里是决定数据源的关键

* 数据源是通过Map结构存的

* 这里返回一个key值 再去获取真正的

* 数据源连接

* >

* @return

*/

@Override

protected Object determineCurrentLookupKey() {

String key = DATA_SOURCE_KEY.get();

LOGGER.info("current data-source is {}", key);

return key;

}

bean 注入:

/**

* 动态的切换类

* <>

* 设置默认的数据源

* >

* @return

* @throws SQLException

*/

@Bean(name = "dynamicDataSource")

public DynamicDataSource dynamicDataSource() throws SQLException {

DynamicDataSource dynamicDataSource = new DynamicDataSource();

dynamicDataSource.setDefaultTargetDataSource(dataSource());

Map dataSourceMap = new HashMap<>(4);

dataSourceMap.put("masterMysql", dataSource());

dataSourceMap.put("dockerMysql", dockerMysqlSource());

dynamicDataSource.setTargetDataSources(dataSourceMap);

return dynamicDataSource;

}

这里主要注意的是setDefaultTargetDataSource和setTargetDataSources,因为AbstractRoutingDataSource维护的数据源是一个Map,在注入DynamicDataSource的时候把你需要连接的数据源放在这个map里面,setDefaultTargetDataSource就是设置默认的数据源。

在把动态数据源配置完成后,还需要配置sqlSession和事物管理,这里不配置的话 注入会出现循环依赖的问题。

/**

* 此配置也需要配置

* 不然会出现循环依赖的问题

* @return

* @throws Exception

*/

@Bean

public SqlSessionFactory sqlSessionFactory() throws Exception {

SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

sqlSessionFactoryBean.setDataSource(dynamicDataSource());

//此处设置为了解决找不到mapper文件的问题

sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));

return sqlSessionFactoryBean.getObject();

}

@Bean

public SqlSessionTemplate sqlSessionTemplate() throws Exception {

return new SqlSessionTemplate(sqlSessionFactory());

}

/**

* 事务管理

*

* @return 事务管理实例

*/

@Bean

public PlatformTransactionManager platformTransactionManager() throws SQLException {

return new DataSourceTransactionManager(dynamicDataSource());

}

到此,动态数据源切换的配置就结束了,网上有许多配置可能会比较麻烦。

3.AOP实现动态数据源切换

在实现配置后,其实实现数据切换就有很多种方法了,你可以直接使用DynamicDataSource在方法里面切换,但是代码侵入性太高了,对整体代码不友好,目前最主流的方法还是使用注解加AOP进行实现。

首先定义切换数据的自定义注解,value代表你存入DynamicDataSourcemap的那个key值

/**

* @description: 数据源动态切换注解 、默认是matserMysql

* @author: dynelm

* @date: 2020-07-03 00:24

**/

@Target({ ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

public @interface DataSource {

String value();

}

例如:@DataSource("slave-master")

定义一个处理切面类:

/**

* @description: 数据源动态切换切面

* @author: dynelm

* @date: 2020-07-03 00:26

**/

@Aspect

@Component

public class DataSourceAspect {

@Before("@annotation(dataSource)")

public void beforeSwitchDataSource(DataSource dataSource){

DynamicDataSource.changeDataSource(dataSource.value());

}

@Before("@annotation(dataSource)")

public void afterSwitchDataSource(DataSource dataSource){

DynamicDataSource.clearDataSource();

}

}

其中@Before("@annotation(dataSource)") 这个是代表在这个注解注的方法进行切面,把注解的value设置进去。

至此,整个动态数据源切换也就实现了,你如果想在方法上使用非默认的数据源的话,只需要在方法上加上@DataSource(value) 这个注解可用,大家可以放心,以及测试过了,保证好用。

三 实现原理分析

其实大家看了上面的实现步骤后,肯定会感觉非常的简单,事实也是非常简单,原来呢,就是一个类,DynamicDataSource 实现了数据源的路由

public class DynamicDataSource extends AbstractRoutingDataSource {

private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

/**

* 使用线程的ThreadLocal 保证多线程的数据源正确

*/

private static final ThreadLocal DATA_SOURCE_KEY = new ThreadLocal<>();

public static void changeDataSource(String dataSourceKey) {

DATA_SOURCE_KEY.set(dataSourceKey);

}

/**

* 需要防止 内存溢出

*/

public static void clearDataSource() {

DATA_SOURCE_KEY.remove();

}

/**

* <>

* 这里是决定数据源的关键

* 数据源是通过Map结构存的

* 这里返回一个key值 再去获取真正的

* 数据源连接

* >

* @return

*/

@Override

protected Object determineCurrentLookupKey() {

String key = DATA_SOURCE_KEY.get();

LOGGER.info("current data-source is {}", key);

return key;

}

首先这里维护了一个ThreadLocal 防止多线程出现并发的数据源问题,通过切面来把注解的value设置到这个这个ThreadLocal里面,其实实现切换的关键就是determineCurrentLookupKey 这个方法,他返回决定了当前需要使用的数据源的key值,然后我们在看一下AbstractRoutingDataSource的源码,看一下他是如何决定当前连接的数据源的:

@Nullable

private Map resolvedDataSources;

/**

* Retrieve the current target DataSource. Determines the

* {@link #determineCurrentLookupKey() current lookup key}, performs

* a lookup in the {@link #setTargetDataSources targetDataSources} map,

* falls back to the specified

* {@link #setDefaultTargetDataSource default target DataSource} if necessary.

* @see #determineCurrentLookupKey()

*/

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

Object lookupKey = determineCurrentLookupKey();

DataSource dataSource = this.resolvedDataSources.get(lookupKey);

if (dataSource == null && (this.lenientFallback || lookupKey == null)) {

dataSource = this.resolvedDefaultDataSource;

}

if (dataSource == null) {

throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");

}

return dataSource;

}

简单来说 就是通过determineCurrentLookupKey 这个方法返回的key去resolvedDataSources里面找对应的DataSource,而这个resolvedDataSources 就是我们配置的

Map dataSourceMap = new HashMap<>(4);

dataSourceMap.put("masterMysql", dataSource());

dataSourceMap.put("dockerMysql", dockerMysqlSource());

dynamicDataSource.setTargetDataSources(dataSourceMap);

是不是,原理就出来的,就是这么实现的。

这里需要注意一下啊,ThreadLocal 在不使用的时候需要进行remove,防止内存泄漏哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值