目录
一、核心类AbstractRoutingDataSource
2、切换操作类DynamicDataSourceHolder
3、配置动态数据源DynamicDataSourceConfig
一、核心类AbstractRoutingDataSource
Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。
实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource :
对于该抽象类,关注两组变量和一个方法:
- Map<Object, Object> targetDataSources和Object defaultTargetDataSource
- Map<Object, DataSource> resolvedDataSources和DataSource resolvedDefaultDataSource
- protected abstract Object determineCurrentLookupKey();
其中两组变量是相互对应的,在熟悉多实例数据源切换代码时不难发现,当有多个数据源的时候,一定要指定一个作为默认的数据源,在这里也同理,当同时初始化多个数据源的时候,需要显示的调用setDefaultTargetDataSource方法指定一个作为默认数据源;
我们需要关注的是
Map<Object, Object> targetDataSources和Map<Object, DataSource> resolvedDataSources,
targetDataSources是暴露给外部程序用来赋值的,用来添加多个数据源实例(DataSource),而resolvedDataSources是程序内部执行时把targetDataSources赋值到resolvedDataSources,因此会有一个赋值的操作,如下图所示:
根据这段源码可以看出,每次执行时,都会遍历targetDataSources内的所有元素并赋值给resolvedDataSources;这样如果我们在外部程序新增一个新的数据源,都会添加到内部使用,从而实现数据源的动态加载。
继承该抽象类的时候,必须实现一个抽象方法:
protected abstract Object determineCurrentLookupKey()
该方法用于指定到底需要使用哪一个数据源。
到此基本上清楚了该抽象类的使用方法,接下来贴下具体的实现代码
二、具体代码实现过程
1、自定义数据源类DynamicDataSource
自定义数据源DataSource类:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @Description 自定义动态数据源类
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSourceType();
}
}
2、切换操作类DynamicDataSourceHolder
通过ThreadLocal维护一个全局唯一的map来实现数据源的动态切换
public class DynamicDataSourceHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
3、配置动态数据源DynamicDataSourceConfig
Druid数据源加载过程图示
对自定义数据源进行配置,在dynamicDataSource()方法上加@Primary注解,优先使用我们自定义的数据源
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @Description 动态数据源配置类
*/
@Configuration // 配置类
public class DynamicDataSourceConfig {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceConfig.class);
@Bean//(initMethod = "init") // 初始化加载
@ConfigurationProperties(prefix = "spring.datasource.master")
public DruidDataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean//(initMethod = "init") // 初始化加载
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DruidDataSource slaveDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Primary//被注解为@Primary的Bean将作为首选者
@Bean
public DataSource dynamicDataSource(DataSource masterDataSource ,DataSource slaveDataSource){
Map<Object,Object> targetDataSource = new HashMap<>();
targetDataSource.put("master",masterDataSource);
targetDataSource.put("slave",slaveDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSource); // 目标数据源列表
dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 默认数据源
// 数据源刷入
// super.afterPropertiesSet();
return dynamicDataSource;
}
/**
* 将动态数据源添加到事务管理器中,并生成新的bean——>待完善
* @return the platform transaction manager
*/
// @Bean
// public PlatformTransactionManager transactionManager() {
// return new DataSourceTransactionManager(dynamicDataSource(masterDataSource(),slaveDataSource()));
// }
}
4、定义注解DataSource
设置动态路由的数据源注解
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "master";
}
5、数据源切换切面DataSourceAspect
使用注解来实现可动态路由的数据源,在每次数据库查询操作前执行
@Aspect
@Component
@Order(-1) // aop顺序,该切面应当先于 @Transactional 执行
public class DataSourceAspect {
@Pointcut("@annotation(com.swadian.spring.dynamicDataSource.DataSource)")
public void dataSourcePointCut(){
// do nothing
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource annotation = method.getAnnotation(DataSource.class);
if(annotation.value() != null){
DynamicDataSourceHolder.setDataSourceType(annotation.value());
}else{
DynamicDataSourceHolder.setDataSourceType("master");
}
try {
return point.proceed();
} finally {
// 清除本次key
DynamicDataSourceHolder.clearDataSourceType();
}
}
}
6、修改启动类->排除自动配置
启动类添加 exclude = {DataSourceAutoConfiguration.class}, 以禁用数据源默认自动配置。
数据源默认自动配置会读取 spring.datasource.* 的属性创建数据源,所以要禁用以进行定制。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(MyApplication.class);
try {
SpringApplication.run(MyApplication.class);
logger.info("springBoot启动成功...");
} catch (Exception e) {
logger.info("SpringBoot启动失败...");
}
}
}
三、附录相关配置文件
application.properties配置文件示例:
#数据源1
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.url=jdbc:mysql://localhost:3306/springbootdemo?useSSL=false
spring.datasource.master.username=root
spring.datasource.master.password=root
#数据源2
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.url=jdbc:mysql://localhost:3306/seckill?useSSL=false
spring.datasource.slave.username=root
spring.datasource.slave.password=root
#映射器,Mapper包下所有的xml文件
mybatis.mapper-locations=classpath:mapper/*.xml
#开启驼峰模式
mybatis.configuration.map-underscore-to-camel-case=true
pom.xml相关依赖示例:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!--切面AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
</dependencies>
参考资料: