服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - SpringBoot多数据源配置的全过程记录

SpringBoot多数据源配置的全过程记录

2022-07-07 09:56装在瓶子里的西班牙阳光 Java教程

在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源,下面这篇文章主要给大家介绍了关于SpringBoot多数据源配置的相关资料,需要的朋友可以参考下

前言

多数据源的核心就是向 ioc 容器注入 abstractroutingdatasource 和如何切换数据源。注入的方式可以是注册 beandefinition 或者是构建好的 bean,切换数据源的方式可以是方法参数或者是注解切换(其他的没想象出来),具体由需求决定。

我的需求是统计多个库的数据,将结果写入另一个数据库,统计的数据库数量是不定的,无法通过 @bean 直接注入,又是统计任务,dao 层注解切换无法满足,因此选择注册(abstractroutingdatasource 的)beandefinition 和方法参数切换来实现。下面以统计统计中日韩用户到结果库为例。

配置文件

master 为结果库,其他为被统计的数据库(china、japan 可以用枚举唯一标识,当然也可以用 string):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dynamic:
  datasources:
    master:
      driver-class-name: com.mysql.cj.jdbc.driver
      url: jdbc:mysql://localhost:3306/result?useunicode=true&characterencoding=utf8xxxxxxxx
      username: root
      password: 123456
    china:
      driver-class-name: com.mysql.cj.jdbc.driver
      url: jdbc:mysql://localhost:3306/china?useunicode=true&characterencoding=utf8xxxxxxxx
      username: root
      password: 123456
    japan:
      driver-class-name: com.mysql.cj.jdbc.driver
      url: jdbc:mysql://localhost:3306/japan?useunicode=true&characterencoding=utf8xxxxxxxx
      username: root
      password: 123456
    korea:
      driver-class-name: com.mysql.cj.jdbc.driver
      url: jdbc:mysql://localhost:3306/korea?useunicode=true&characterencoding=utf8xxxxxxxx
      username: root
      password: 123456

对应的配置类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.statistics.dynamicds.core.config;
 
import com.statistics.dynamicds.core.country;
import lombok.data;
import org.springframework.boot.context.properties.configurationproperties;
import org.springframework.context.annotation.configuration;
 
import java.util.map;
 
import static com.statistics.dynamicds.core.config.multidatasourceproperties.prefix;
 
@data
@configuration
@configurationproperties(prefix = prefix)
public class multidatasourceproperties {
  public static final string prefix = "dynamic";
  private map<country, datasourceproperties> datasources;
 
  @data
  public static class datasourceproperties {
    private string driverclassname;
    private string url;
    private string username;
    private string password;
  }
}
package com.statistics.dynamicds.core;
 
public enum country {
  master("master", 0),
 
  china("china", 86),
  japan("japan", 81),
  korea("korea", 82),
  // 其他国家省略
 
  private final string name;
  private final int id;
 
  country(string name, int id) {
    this.name = name;
    this.id = id;
  }
 
  public int getid() {
    return id;
  }
 
  public string getname() {
    return name;
  }
}

依赖

orm 用的 jpa,springboot 版本为 2.3.7.release,通过 lombok 简化 getset。

?
1
2
3
4
5
6
7
8
9
10
11
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>
 
<dependency>
     <groupid>org.projectlombok</groupid>
     <artifactid>lombok</artifactid>
     <version>1.18.22</version>
     <scope>provided</scope>
</dependency>

构建 abstractroutingdatasource

spring 的动态数据源需要注入 abstractroutingdatasource,因为配置文件中被统计数据源不是固定的,所以不能通过 @bean 注解注入,需要手动构建。

要在启动类加上 @import(multidatasourceimportbeandefinitionregistrar.class)。

要在启动类加上 @import(multidatasourceimportbeandefinitionregistrar.class)。

要在启动类加上 @import(multidatasourceimportbeandefinitionregistrar.class),重要的事情写三行。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.statistics.dynamicds.autoconfig;
 
import com.statistics.dynamicds.core.dynamicdatasourcerouter;
import com.statistics.dynamicds.core.country;
import com.statistics.dynamicds.core.config.multidatasourceproperties;
import com.zaxxer.hikari.hikaridatasource;
import org.springframework.beans.factory.support.abstractbeandefinition;
import org.springframework.beans.factory.support.beandefinitionbuilder;
import org.springframework.beans.factory.support.beandefinitionregistry;
import org.springframework.boot.context.properties.bind.binder;
import org.springframework.boot.jdbc.datasourcebuilder;
import org.springframework.context.environmentaware;
import org.springframework.context.annotation.importbeandefinitionregistrar;
import org.springframework.core.env.environment;
import org.springframework.core.type.annotationmetadata;
 
import javax.annotation.nonnull;
import java.util.map;
import java.util.stream.collectors;
 
import static com.statistics.dynamicds.core.config.multidatasourceproperties.prefix;
 
public class multidatasourceimportbeandefinitionregistrar implements importbeandefinitionregistrar, environmentaware {
  public static final string datasource_beanname = "dynamicdatasourcerouter";
  private environment environment;
 
  @override
  public void registerbeandefinitions(@nonnull annotationmetadata importingclassmetadata, beandefinitionregistry registry) {
    multidatasourceproperties multidatasourceproperties = binder.get(environment)
            .bind(prefix, multidatasourceproperties.class)
            .orelsethrow(() -> new runtimeexception("no found dynamicds config"));
    final hikaridatasource[] defaulttargetdatasource = {null};
    map<country, hikaridatasource> targetdatasources = multidatasourceproperties.getdatasources().entryset().stream()
            .collect(collectors.tomap(
                    map.entry::getkey,
                    entry -> {
              multidatasourceproperties.datasourceproperties datasourceproperties = entry.getvalue();
              hikaridatasource datasource = datasourcebuilder.create()
                      .type(hikaridatasource.class)
                      .driverclassname(datasourceproperties.getdriverclassname())
                      .url(datasourceproperties.geturl())
                      .username(datasourceproperties.getusername())
                      .password(datasourceproperties.getpassword())
                      .build();
              datasource.setpoolname("hikaripool-" + entry.getkey());
              if (country.master == entry.getkey()) {
                defaulttargetdatasource[0] = datasource;
              }
              return datasource;
            }));
    abstractbeandefinition beandefinition = beandefinitionbuilder.genericbeandefinition(dynamicdatasourcerouter.class)
            .addconstructorargvalue(defaulttargetdatasource[0])
            .addconstructorargvalue(targetdatasources)
            .getbeandefinition();
    registry.registerbeandefinition(datasource_beanname, beandefinition);
  }
 
  @override
  public void setenvironment(@nonnull environment environment) {
    this.environment = environment;
  }
}

上面代码中 multidatasourceproperties 不是由 @resource 或者 @autowired 获取的是因为 importbeandefinitionregistrar 执行的很早,此时 @configurationproperties 的配置参数类还没有注入,因此要手动获取(加 @configurationproperties 注解是为了使 ioc 容器中其他 bean 能获取配置的 country,以此来切换数据源)。

下面是 abstractroutingdatasource 的实现类 dynamicdatasourcerouter:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.statistics.dynamicds.core;
 
import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;
 
import java.util.map;
 
public class dynamicdatasourcerouter extends abstractroutingdatasource {
  public dynamicdatasourcerouter(object defaulttargetdatasource, map<object, object> targetdatasources) {
    this.setdefaulttargetdatasource(defaulttargetdatasource);
    this.settargetdatasources(targetdatasources);
  }
 
  @override
  protected object determinecurrentlookupkey() {
    return datasourcecontextholder.getlookupkey();
  }
}

数据源切换

数据源的切换由 datasourcecontextholder 和切面 dynamicdatasourceaspect 控制:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.statistics.dynamicds.core;
 
public class datasourcecontextholder {
  private static final threadlocal<country> holder = threadlocal.withinitial(() -> country.master);
 
  public static void setlookupkey(country lookupkey) {
    holder.set(lookupkey);
  }
 
  public static country getlookupkey() {
    return holder.get();
  }
 
  public static void clear() {
    holder.remove();
  }
}
package com.statistics.dynamicds.core;
 
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.around;
import org.aspectj.lang.annotation.aspect;
import org.aspectj.lang.annotation.pointcut;
import org.springframework.stereotype.component;
 
@aspect
@component
public class dynamicdatasourceaspect {
 
  @pointcut("execution(* com.statistics.dao..*.*(..))")
  void aspect() {
 
  }
 
  @around("aspect()")
  public object around(proceedingjoinpoint joinpoint) throws throwable {
    for (object arg : joinpoint.getargs()) {
      if (arg instanceof country) {
        datasourcecontextholder.setlookupkey((country) arg);
        break;
      }
    }
    try {
      return joinpoint.proceed();
    }finally {
      datasourcecontextholder.clear();
    }
  }
}

目录

.
└─com
    └─statistics
        │  statisticsapplication.java
        │
        ├─dao
        │      userdao.java
        │
        ├─dynamicds
        │  ├─autoconfig
        │  │      multidatasourceimportbeandefinitionregistrar.java
        │  │
        │  └─core
        │      │  datasourcecontextholder.java
        │      │  dynamicdatasourceaspect.java
        │      │  dynamicdatasourcerouter.java
        │      │  province.java
        │      │
        │      └─config
        │              multidatasourceproperties.java

总结

以上就完成了多数据源配置,使用时只需要按照在 dao 层的方法参数中加一个 country 枚举就可以了。

如果无法用枚举标识数据源也可以换成 string,关于这个数据源的其他信息在内部类 datasourceproperties 加一个 map 即可,总之就是按照自己的需求扩展。

原文链接:https://www.cnblogs.com/hligy/p/15507306.html

延伸 · 阅读

精彩推荐