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

2025-05-29 0 72

前言

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

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

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

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

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

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

简单分析下 abstractroutingdatasource 的源码

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

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

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

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

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

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

通过下面的 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

总结

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

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

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 spring实现动态切换、添加数据源及源码分析 https://www.kuaiidc.com/111056.html

相关文章

发表评论
暂无评论