前言
对于数据量在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