好得很程序员自学网

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

spring实现动态切换、添加数据源及源码分析

前言

对于数据量在1千万,单个mysql数据库就可以支持,但是如果数据量大于这个数的时候,例如1亿,那么查询的性能就会很低。此时需要对数据库做水平切分,常见的做法是按照用户的账号进行hash,然后选择对应的数据库。

最近公司项目需求,由于要兼容老系统的数据库结构,需要搭建一个 可以动态切换、添加数据源的后端服务。

参考了过去的项目,通过配置多个sqlsessionfactory 来实现多数据源,这么做的话,未免过于笨重,而且无法实现动态添加数据源这个需求

通过 spring abstractroutingdatasource 为我们抽象了一个 dynamicdatasource 解决这一问题

简单分析下 abstractroutingdatasource 的源码

targetdatasources 就是我们的多个数据源,在初始化的时候会调用afterpropertiesset(),去解析我们的数据源 然后 put 到 resolveddatasources

实现了 datasource 的 getconnection(); 我们看看 determinetargetdatasource(); 做了什么

通过下面的 determinecurrentlookupkey();(这个方法需要我们实现) 返回一个key,然后从 resolveddatasources (其实也就是 targetdatasources) 中 get 一个数据源,实现了每次调用 getconnection(); 打开连接 切换数据源,如果想动态添加的话 只需要重新 set targetdatasources 再调用 afterpropertiesset() 即可

talk is cheap. show me the code

我使用的springboot版本为 1.5.x,下面是核心代码

?

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

/**

  * 多数据源配置

  *

  * @author taven

  *

  */

@configuration

@mapperscan ( "com.gitee.taven.mapper" )

public class datasourceconfigurer {

 

  /**

   * datasource 自动配置并注册

   *

   * @return data source

   */

  @bean ( "db0" )

  @primary

  @configurationproperties (prefix = "datasource.db0" )

  public datasource datasource0() {

   return druiddatasourcebuilder.create().build();

  }

 

  /**

   * datasource 自动配置并注册

   *

   * @return data source

   */

  @bean ( "db1" )

  @configurationproperties (prefix = "datasource.db1" )

  public datasource datasource1() {

   return druiddatasourcebuilder.create().build();

  }

 

  /**

   * 注册动态数据源

   *

   * @return

   */

  @bean ( "dynamicdatasource" )

  public datasource dynamicdatasource() {

   dynamicroutingdatasource dynamicroutingdatasource = new dynamicroutingdatasource();

   map<object, object> datasourcemap = new hashmap<>();

   datasourcemap.put( "dynamic_db0" , datasource0());

   datasourcemap.put( "dynamic_db1" , datasource1());

   dynamicroutingdatasource.setdefaulttargetdatasource(datasource0()); // 设置默认数据源

   dynamicroutingdatasource.settargetdatasources(datasourcemap);

   return dynamicroutingdatasource;

  }

 

  /**

   * sql session factory bean.

   * here to config datasource for sqlsessionfactory

   * <p>

   * you need to add @{@code @configurationproperties(prefix = "mybatis")}, if you are using *.xml file,

   * the {@code 'mybatis.type-aliases-package'} and {@code 'mybatis.mapper-locations'} should be set in

   * {@code 'application.properties'} file, or there will appear invalid bond statement exception

   *

   * @return the sql session factory bean

   */

  @bean

  @configurationproperties (prefix = "mybatis" )

  public sqlsessionfactorybean sqlsessionfactorybean() {

   sqlsessionfactorybean sqlsessionfactorybean = new sqlsessionfactorybean();

   // 必须将动态数据源添加到 sqlsessionfactorybean

   sqlsessionfactorybean.setdatasource(dynamicdatasource());

   return sqlsessionfactorybean;

  }

 

  /**

   * 事务管理器

   *

   * @return the platform transaction manager

   */

  @bean

  public platformtransactionmanager transactionmanager() {

   return new datasourcetransactionmanager(dynamicdatasource());

  }

}

通过 threadlocal 获取线程安全的数据源 key

?

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

package com.gitee.taven.config;

 

public class dynamicdatasourcecontextholder {

 

  private static final threadlocal<string> contextholder = new threadlocal<string>() {

   @override

   protected string initialvalue() {

    return "dynamic_db0" ;

   }

  };

 

  /**

   * to switch datasource

   *

   * @param key the key

   */

  public static void setdatasourcekey(string key) {

   contextholder.set(key);

  }

 

  /**

   * get current datasource

   *

   * @return data source key

   */

  public static string getdatasourcekey() {

   return contextholder.get();

  }

 

  /**

   * to set datasource as default

   */

  public static void cleardatasourcekey() {

   contextholder.remove();

  }

}

动态 添加、切换数据源

?

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

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

/**

  * 动态数据源

  *

  * @author taven

  *

  */

public class dynamicroutingdatasource extends abstractroutingdatasource {

 

  private final logger logger = loggerfactory.getlogger(getclass());

 

  private static map<object, object> targetdatasources = new hashmap<>();

 

  /**

   * 设置当前数据源

   *

   * @return

   */

  @override

  protected object determinecurrentlookupkey() {

   logger.info( "current datasource is [{}]" , dynamicdatasourcecontextholder.getdatasourcekey());

   return dynamicdatasourcecontextholder.getdatasourcekey();

  }

 

  @override

  public void settargetdatasources(map<object, object> targetdatasources) {

   super .settargetdatasources(targetdatasources);

   dynamicroutingdatasource.targetdatasources = targetdatasources;

  }

 

  /**

   * 是否存在当前key的 datasource

   *

   * @param key

   * @return 存在返回 true, 不存在返回 false

   */

  public static boolean isexistdatasource(string key) {

   return targetdatasources.containskey(key);

  }

 

  /**

   * 动态增加数据源

   *

   * @param map 数据源属性

   * @return

   */

  public synchronized boolean adddatasource(map<string, string> map) {

   try {

    connection connection = null ;

    // 排除连接不上的错误

    try {

     class .forname(map.get(druiddatasourcefactory.prop_driverclassname));

     connection = drivermanager.getconnection(

       map.get(druiddatasourcefactory.prop_url),

       map.get(druiddatasourcefactory.prop_username),

       map.get(druiddatasourcefactory.prop_password));

     system.out.println(connection.isclosed());

    } catch (exception e) {

     return false ;

    } finally {

     if (connection != null && !connection.isclosed())

      connection.close();

    }

    string database = map.get( "database" ); //获取要添加的数据库名

    if (stringutils.isblank(database)) return false ;

    if (dynamicroutingdatasource.isexistdatasource(database)) return true ;

    druiddatasource druiddatasource = (druiddatasource) druiddatasourcefactory.createdatasource(map);

    druiddatasource.init();

    map<object, object> targetmap = dynamicroutingdatasource.targetdatasources;

    targetmap.put(database, druiddatasource);

    // 当前 targetdatasources 与 父类 targetdatasources 为同一对象 所以不需要set

//   this.settargetdatasources(targetmap);

    this .afterpropertiesset();

    logger.info( "datasource {} has been added" , database);

   } catch (exception e) {

    logger.error(e.getmessage());

    return false ;

   }

   return true ;

  }

}

可以通过 aop 或者 手动 dynamicdatasourcecontextholder.setdatasourcekey(string key) 切换数据源

需要注意的:当我们开启了事务之后,是无法在去切换数据源的

本文项目源码:https://gitee.com/yintianwen7/spring-dynamic-datasource

参考文献:https://github.com/helloworlde/springboot-dynamicdatasource

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

原文链接:https://www.jianshu.com/p/0a485c965b8b

查看更多关于spring实现动态切换、添加数据源及源码分析的详细内容...

  阅读:52次