SpringBoot+druid+aop动态数据源切换实现-修改中

目录

一、核心类AbstractRoutingDataSource

二、具体代码实现过程

1、自定义数据源类DynamicDataSource 

2、切换操作类DynamicDataSourceHolder

3、配置动态数据源DynamicDataSourceConfig

4、定义注解DataSource

5、数据源切换切面DataSourceAspect

6、修改启动类->排除自动配置

三、附录相关配置文件


一、核心类AbstractRoutingDataSource

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。

实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。 

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

对于该抽象类,关注两组变量和一个方法:

  1. Map<Object, Object> targetDataSourcesObject defaultTargetDataSource
  2. Map<Object, DataSource> resolvedDataSourcesDataSource resolvedDefaultDataSource
  3. protected abstract Object determineCurrentLookupKey();

其中两组变量是相互对应的,在熟悉多实例数据源切换代码时不难发现,当有多个数据源的时候,一定要指定一个作为默认的数据源,在这里也同理,当同时初始化多个数据源的时候,需要显示的调用setDefaultTargetDataSource方法指定一个作为默认数据源; 

我们需要关注的是

Map<Object, Object> targetDataSourcesMap<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>

参考资料: