天天看点

Spring Boot实现动态切换数据源

之前也实现了多数据源功能,是将包路径绑定对应的数据源,调用该包下的方法会走对应的数据库,这种方法不太灵活,后面我改造了一下,可实现动态切换数据源。

之前实现多数据源写法:https://blog.csdn.net/qq_43037478/article/details/109601688

项目结构

Spring Boot实现动态切换数据源

1.数据源配置文件

 在application.yml配置文件中配置两个数据源,如下:

datasource:
    pre :
      jdbc-url: jdbc:mysql://localhost:3306/xiaxia1?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8
      username: root
      password: 123456
    sit :
      jdbc-url: jdbc:mysql://localhost:3306/xiaxia2?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8
      username: root
      password: 123456
           
注:按层次结构写的配置文件,一定要注意缩进以及表达的含义。
           

2.修改springboot服务启动文件:Application.java

// exclude = {DataSourceAutoConfiguration.class} 排除主动注入数据源
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
//@EnableTransactionManagement // 开启注解事务管理,等同于xml配置文件中的 <tx:annotation-driven />
//自动扫描包路径下面的所有@Controller、@Service、@Repository、@Component 的类,
// 并把符合扫描规则的类装配到spring容器中。
@ComponentScan(basePackages = {"com.xiateng"})
// 原来扫描dao层接口
@MapperScan({"com.xiateng.dao.user","com.xiateng.dao.userbuyer"})
public class Application extends WebMvcConfigurerAdapter{

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SessionInterceptor()).excludePathPatterns(Arrays.asList("/res/**"));
    }
}
           

配置说明:exclude = {DataSourceAutoConfiguration.class}

exclude,排除此类的AutoConfig,即禁止 SpringBoot 自动注入数据源配置,DataSourceAutoConfiguration.class 会自动查找 application.yml 或者 properties 文件里的 spring.datasource.* 相关属性并自动配置单数据源,因为这里我们需要手动配置多数据源

3.配置两个数据源(数据源个数看自己需要)

@Configuration
public class DataSourceConfig {

    // 表示这个数据源是默认数据源
//    @Primary
    // 将这个对象放入spring容器中(交给Spring管理)
    @Bean(name="preDataSource")
    // 读取 application.yml 中的配置参数映射成为一个对象
    @ConfigurationProperties(prefix = "spring.datasource.pre")
    public DataSource getDataSource1(){
        // 创建一个数据源
        return DataSourceBuilder.create().build();
    }

//    @Primary
    @Bean(name="sitDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.sit")
    public DataSource getDataSource2(){
        return DataSourceBuilder.create().build();
    }
}
           

4.数据源路由类,用于动态获取数据源

 数据源上下文,用于保存跟获取Spring容器中的数据源Bean

/**
 * 数据源上下文
 */
public class DataSourceContext {

    // 用于记录每个线程需要使用的数据源关键字。并提供切换、读取、清除数据源配置信息的方法
    private  static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String value) {
        contextHolder.set(value);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}
           
/**
 * DataSource 路由类
 * 重写的函数决定了最后选择的DateSource
 */
public class MultiRouteDataSource extends AbstractRoutingDataSource{

    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        // 通过绑定线程的数据源上下文实现多数据源的动态切换,有兴趣的可以去查阅资料或源码
        return DataSourceContext.getDataSource();
    }
}
           

AbstractRoutingDataSource说明

AbstractRoutingDataSource是spring-jdbc包提供的一个了AbstractDataSource的抽象类,它实现了DataSource接口的用于获取数据库连接的方法。

AbstractRoutingDataSource的内部维护了一个名为targetDataSources的Map,并提供的setter方法用于设置数据源关键字与数据源的关系,实现类被要求实现其determineCurrentLookupKey()方法,由此方法的返回值决定具体从哪个数据源中获取连接。

AbstractRoutingDataSource提供了程序运行时动态切换数据源的方法,在dao类或方法上标注需要访问数据源的关键字,路由到指定数据源,获取连接。

 五.数据源配置类(注入数据源),设置多个数据源,同时设置默认数据源

@Configuration
public class DataSourceComponent {

    @Autowired
    @Qualifier("preDataSource")
    private DataSource preDataSource;
    @Autowired
    @Qualifier("sitDataSource")
    private DataSource sitDataSource;

    @Primary//不加这个会报错。
    @DependsOn({ "preDataSource", "sitDataSource"}) //解决数据库循环依赖问题
    @Bean(name = "multiDataSource")
    public MultiRouteDataSource exampleRouteDataSource() {
        MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("preDataSource", preDataSource);
        targetDataSources.put("sitDataSource", sitDataSource);
        multiDataSource.setTargetDataSources(targetDataSources);
        multiDataSource.setDefaultTargetDataSource(preDataSource);
        return multiDataSource;
    }
}
           

 写到这里大家会有个疑问,为什么要同时用的两个注解@Autowired和@Qualifier("preDataSource"),直接用一个不就可以吗,答案是否定的。

@Autowired : 从Spring容器中找到类型为DataSource的类,将它注入进来。

@Qualifier("preDataSource") : 指定我们需要装配的Bean。

了解了这两个注解的功能后我恍然大悟,我配置多数据源的时候,DataSource这个类在Spring容器中是存在两个不同名称的Bean的(preDataSource和sitDataSource),如果直接用@Autowired注解,Spring不知道要装配哪个Bean,项目启动就会报错,而@Qualifier("preDataSource")可以指定装配哪个Bean,这样就理解了。。。。

六.测试

public int multiRouteTest(TtUser record) {
        // 切换数据源为sitDataSource(我之前配置默认数据源是preDataSource)
        DataSourceContext.setDataSource("sitDataSource");
        int i = 0;
        try {
            record.setUpdatedBy(54543L);
            i = ttUserMapper.updateByPrimaryKeySelective(record);
        }catch (Exception e){
            e.printStackTrace();
        }

        return i;
    }
           

这样就实现了数据源动态切换 ☺☺☺☺☺☺

配置多数据源固定写法请参照:https://blog.csdn.net/qq_43037478/article/details/109601688

继续阅读