SpringBoot 动态数据源配置(附带可执行Demo)

举报
i进击的攻城狮 发表于 2022/10/30 11:53:33 2022/10/30
【摘要】 前言一般我们开发的单体项目中,都是一个前端,一个后端,一个数据库。但在实际的应用开发中,有时候,一个后端会同时用到多个数据库。这时候可能就会需要用到动态数据源。之前公司有一个类似的业务,这是一个数据处理的系统,后端会接收不同类型的数据,不同的数据,要根据不同的数据类型,存储查询到不同的数据库中,当时就是通过使用Spring动态数据源+aop进行实现的。 一、实现原理在spring动态数据源配...

前言

一般我们开发的单体项目中,都是一个前端,一个后端,一个数据库。但在实际的应用开发中,有时候,一个后端会同时用到多个数据库。这时候可能就会需要用到动态数据源。
之前公司有一个类似的业务,这是一个数据处理的系统,后端会接收不同类型的数据,不同的数据,要根据不同的数据类型,存储查询到不同的数据库中,当时就是通过使用Spring动态数据源+aop进行实现的。

一、实现原理

在spring动态数据源配置中,涉及到的类有:

  • DriverManagerDataSource 负责封装数据连接的参数,数据库的url,username和password,在多数据源的配置中,spring会管理多个DriverManagerDataSource 的对象,对象存储到AbstractRoutingDataSource 类中,通过key-value的形式进行存储。
    在这里插入图片描述

  • AbstractRoutingDataSource 是实现动态数据源的关键,通过实现这个类的determineCurrentLookupKey()方法,决定了当线程进入后,jdbc调用哪个数据库,实现类交给spring管理,并为其配置多个数据源,

  • ThreadLocal 存储线程的数据源key,通过key去找到对应的datasource

  • 切面,通过自定义注解定义切面类,在注解中,通过参数灵活的定义接口访问的数据源。

我们想连接一个数据库,在JDBC中,我们需要为JDBC提供数据库的个url,userName,和password三个参数:如

jdbc:mysql://localhost:3306/t_db1?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC

有了这三个连接数据库的参数后,我们就能用这三个参数,构建DataSource类,这是个接口,在spring中的继承关系如下
在这里插入图片描述

二、Demo

文件描述有点苍白,建议结合demo代码去理解。

1、创建数据

创建三个不同的数据库,在数据库中创建三张相同的表,表里面随便写几条数据进去。

-- auto-generated definition
create table t_test
(
    id   int auto_increment
        primary key,
    name text   null,
    age  double null
);

在这里插入图片描述

2、定义数据源

一般是在application里面去定义我们的数据源配置,数据源的配置比如说datasource01,default是我们自己定义的参数,并不是springboot内置的。default代表默认的数据源,即没有使用注解,找不到对应数据源时应该使用哪个。这里要注意,为什么值是dataSource3而不是dataSource03,这个后面再说,并不是写错了。

spring:
  datasource:
    # 可以自己定义
    datasource01:
      username: root
      password: xxx
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/t_db1?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    datasource02:
      username: root
      password: xxx
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/t_db2?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    datasource03:
      username: root
      password: xxx
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/t_db3?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    default: dataSource3

3、创建数据库实体类

这个没啥好说的

public class TestBean {
    private Integer id;
    private String name;
    private Double age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getAge() {
        return age;
    }

    public void setAge(Double age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "TestBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4、将数据源交给spring管理

通过@Configuration注解和@ConfigurationProperties注解,让spring能读到装载了参数的bean。
这里粘一个就行了,剩下的自己补充。

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.datasource01")
public class DataSource01 {

    String url;
    String userName;
    String password;
}

5、通过ThreadLocal管理数据源配置

为什么要使用ThreadLocal呢?因为在服务器端,JDBC只有一份,我们需要动态的让JDBC使用某一个数据源,所以,在不同线程进入服务的时候,把线程所需要使用的数据源的信息,通过ThreadLocal进行隔离。

public class DataSourceKeyHolder {
    private static final ThreadLocal<String> keyHolder = new ThreadLocal<>();

    public static void setKey(String key) {
        keyHolder.remove();
        keyHolder.set(key);
    }

    public static String getKey() {
        String s = keyHolder.get();
        return s;
    }
    public static void clear(){
        keyHolder.remove();
    }
}

6、继承AbstractRoutingDataSource抽象类

继承这个类是1关键,动态数据源就是通过调用determineCurrentLookupKey确定数据源的bean名称,这个类将会给spring管理。

public class MyDynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceKeyHolder.getKey();
    }
}

7、配置Spring管理相关Bean

注意下面管理的Bean有哪些,分别是三个数据库数据源的实体类,MyDynamicDataSource类,这个类通过map的形式,装载数据源实体。JdbcTemplate,JDBC通过参数AbstractRoutingDataSource类,确定数据源。
这里注意的是,数据源实体类的方法名,就是配置文件里面设置默认数据源里配置。

@Configuration
public class Config {

    @Autowired
    private DataSource01 dataSource1;
    @Autowired
    private DataSource02 dataSource2;
    @Autowired
    private DataSource03 dataSource3;

    @Value("${spring.datasource.default}")
    private String def;
    @Bean
    public DriverManagerDataSource dataSource1(){
        String url = dataSource1.getUrl();
        String userName = dataSource1.getUserName();
        String password = dataSource1.getPassword();
        return new DriverManagerDataSource(url,userName,password);
    }
    @Bean
    public DriverManagerDataSource dataSource2(){
        String url = dataSource2.getUrl();
        String userName = dataSource2.getUserName();
        String password = dataSource2.getPassword();
        return new DriverManagerDataSource(url,userName,password);
    }
    @Bean
    public DriverManagerDataSource dataSource3(){
        String url = dataSource3.getUrl();
        String userName = dataSource3.getUserName();
        String password = dataSource3.getPassword();
        return new DriverManagerDataSource(url,userName,password);
    }
    @Bean
    public MyDynamicDataSource dynamicDataSource(Map<String,DriverManagerDataSource> map){
        HashMap<Object, Object> original = new HashMap<>(map);
        MyDynamicDataSource myDynamicDataSource = new MyDynamicDataSource();
        myDynamicDataSource.setDefaultTargetDataSource(map.get(def));
        myDynamicDataSource.setTargetDataSources(original);
        return myDynamicDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(AbstractRoutingDataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

8、配置AOP

/**
 * 类描述: 设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上
 *
 */

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


@Aspect
@Order(1)
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.dynamicdemo.dynamic.asp.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) {
            DataSourceKeyHolder.setKey(dataSource.value());
        }
        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
            DataSourceKeyHolder.clear();
        }
    }
}

9、实现接口,用来测试

@RestController
public class TestController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("/source1")
    @DataSource(value = "dataSource1")
    public Object local(){
        TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1);

        return testBean;
    }
    @GetMapping("/source2")
    @DataSource(value = "dataSource2")
    public List<Map<String, Object>> remote(){
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from t_test");
        return maps;
    }
    @GetMapping("/source3")
    @DataSource(value = "dataSource3")
    public List<Map<String, Object>> remote1(){
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from t_test");
        return maps;
    }

    @GetMapping("/source4")
    public List<Map<String, Object>> remote4(){
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from t_test");
        return maps;
    }

}

三、 测试

接口中的方法,sql内容都是相同的,但是访问每个接口的结果却不相同。
在这里插入图片描述
在这里插入图片描述
很明显,他们访问的数据库并不相同。


如果不想敲代码,可以直接从github下代码下来 点击这里
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。