好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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测试数据ponent;

 

@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://HdhCmsTestcnblogs测试数据/hligy/p/15507306.html

查看更多关于SpringBoot多数据源配置的全过程记录的详细内容...

  阅读:12次