数据源切换方法
spring对数据源的管理类似于策略模式,不懂策略模式也没关系,其实就是有一个全局的键值对,类型是map<string, datasource>。当jdbc操作数据库之时,会根据不同的key值选择不同的数据源。而这个key值可以放到方法的注解里。
所以切换数据源的思路就是让jdbc在获取数据源时根据key获取到要切换的数据源。
jdbc提供了abstractroutingdatasource抽象类,类名意思是数据源路由,该类提供了一个抽象方法determinecurrentlookupkey(),切换数据源时jdbc会调用这个方法获取数据源的key,所以只需要实现该方法,改变该方法中返回的key值即可。
源码解读
1.从类关系图中可以看出abstractroutingdatasource类实现了datasource接口,后者有两个getconnection()方法,即获取db连接的作用。

2.abstractroutingdatasource实现了这两个方法

其中determinetargetdatasource()方法的作用就是获取实际的数据源,其内部调用了determinecurrentlookupkey()方法,取到当前设定的key,通过key在上下文this.resolveddatasources属性中尝试获取datasource对象,这个对象即当前连接的数据源

3.那么this.resolveddatasources在哪里维护呢? 继续在abstractroutingdatasource类里找,可以找到afterpropertiesset()方法,这个方法是initializingbean接口的,作用是在bean的所有属性设置完成后便会调用此方法。可以看到this.resolveddatasources是从this.targetdatasources取的信息。

所以只需要改变this.targetdatasources,即可改变this.resolveddatasources;后续改变determinecurrentlookupkey()的返回值(key),在调用getconnection()时即可获取到指定的数据源
实现方式:注解+切面
别看步骤挺多,但其实很容易理解和使用
1.配置文件示例:
spring:
datasource:
master: # 数据源master
jdbc-url: jdbc:mysql://localhost:3306/master?characterencoding=utf8&useunicode=true&usessl=false&servertimezone=gmt%2b8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.driver
db1: # 数据源1
jdbc-url: jdbc:mysql://localhost:3306/db2?characterencoding=utf8&useunicode=true&usessl=false&servertimezone=gmt%2b8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.driver
2.创建数据源配置类
创建数据源配置类(我这里为了方便区分就为每一个数据源创建了一个配置类,当然也可以把所有的数据源配置在一个类里)
@configuration
@enableconfigurationproperties({masterdatasourceproperties.class})
public class masterdatasourceconfig {
/**
* 这个masterdatasourceproperties是读取配置文件的类,我这里为了省篇幅就不展示了
**/
@autowired
private masterdatasourceproperties masterdatasourceproperties;
@bean
@primary
public datasource masterdatasource() {
druiddatasource datasource = new druiddatasource();
datasource.seturl(masterdatasourceproperties.geturl());
datasource.setusername(masterdatasourceproperties.getusername());
datasource.setpassword(aesutil.aesdecode(masterdatasourceproperties.getpassword()));
datasource.setdriverclassname(masterdatasourceproperties.getdriverclassname());
......
return datasource;
}
}
@configuration
@enableconfigurationproperties({odsdatasourceproperties.class})
public class db1datasourceconfig {
/**
* 这个db1datasourceproperties是读取配置文件的类,我这里为了省篇幅就不展示了
**/
@autowired
private db1datasourceproperties db1datasourceproperties;
@bean
public datasource db1datasource() {
druiddatasource datasource = new druiddatasource();
datasource.seturl(db1datasourceproperties.geturl());
datasource.setusername(db1datasourceproperties.getusername());
datasource.setpassword(aesutil.aesdecode(db1datasourceproperties.getpassword()));
datasource.setdriverclassname(db1datasourceproperties.getdriverclassname());
......
return datasource;
}
}
3.创建dynamicdatasource
创建自己的一个dynamicdatasource类(名字任意)继承abstractroutingdatasource,维护数据源,提供切换方法。
public class dynamicdatasource extends abstractroutingdatasource {
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个datasource实现类对象即可
*/
@override
protected datasource determinetargetdatasource() {
return super.determinetargetdatasource();
}
/**
* 如果希望所有数据源在启动配置时就加载好,然后通过设置数据源key值来切换数据,定制这个方法
*/
@override
protected object determinecurrentlookupkey() {
return dynamicdatasourcecontextholder.getdatasourcekey();
}
/**
* 设置默认数据源
*
* @param defaultdatasource
*/
public void setdefaultdatasource(object defaultdatasource) {
super.setdefaulttargetdatasource(defaultdatasource);
}
/**
* 设置数据源
*
* @param datasources
*/
public void setdatasources(map<object, object> datasources) {
super.settargetdatasources(datasources);
// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
dynamicdatasourcecontextholder.adddatasourcekeys(datasources.keyset());
}
}
4.创建数据源上下文处理器dynamicdatasourcecontextholder
创建数据源上下文处理器dynamicdatasourcecontextholder用以存储当前线程需要使用的数据源名称。
public class dynamicdatasourcecontextholder {
private static final threadlocal<string> contextholder = new threadlocal<string>() {
/**
* 将 master 数据源的 key作为默认数据源的 key
*/
@override
protected string initialvalue() {
return "master";
}
};
/**
* 数据源的 key集合,用于切换时判断数据源是否存在
*/
public static list<object> datasourcekeys = new arraylist<>();
/**
* 切换数据源
*
* @param key
*/
public static void setdatasourcekey(string key) {
contextholder.set(key);
}
/**
* 获取数据源
*
* @return
*/
public static string getdatasourcekey() {
return contextholder.get();
}
/**
* 重置数据源
*/
public static void cleardatasourcekey() {
contextholder.remove();
}
/**
* 判断是否包含数据源
*
* @param key 数据源key
* @return
*/
public static boolean containdatasourcekey(string key) {
return datasourcekeys.contains(key);
}
/**
* 添加数据源keys
*
* @param keys
* @return
*/
public static boolean adddatasourcekeys(collection<? extends object> keys) {
return datasourcekeys.addall(keys);
}
}
5.创建数据源配置类datasourceconfig
创建数据源配置类datasourceconfig,将所有数据源注入到spring容器
@configuration
@enableautoconfiguration(exclude = { datasourceautoconfiguration.class }) // 排除 datasourceautoconfiguration 的自动配置,避免环形调用
public class datasourceconfig {
@autowired
private masterdatasourceconfig masterdatasourceconfig;
@autowired
private db1datasourceconfig db1datasourceconfig;
/**
* 设置动态数据源为主数据源
*
* @return
*/
@bean
@primary
public dynamicdatasource datasource() {
dynamicdatasource dynamicdatasource = new dynamicdatasource();
// 默认指定的数据源
dynamicdatasource.setdefaultdatasource(masterdatasourceconfig.masterdatasource());
// 将数据源设置进map
map<object, object> datasourcemap = new hashmap<>(8);
datasourcemap.put(datasourceenum.master.tostring(), masterdatasourceconfig.masterdatasource());
datasourcemap.put(datasourceenum.db1.tostring(), db1datasourceconfig.db1datasource());
// 使用 map 保存多个数据源,并设置到动态数据源对象中,这个值最终会在afterpropertiesset中被设置到resolveddatasources上
dynamicdatasource.setdatasources(datasourcemap);
return dynamicdatasource;
}
}
6.创建数据源类型枚举datasourceenum
public enum datasourceenum {
/**默认类型*/
master,
/**db1类型*/
db1,
;
}
7.创建自定义注解@datasource
@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
@documented
public @interface datasource {
/**
* 数据源key值
* @return
*/
datasourceenum value();
}
8.创建切面dynamicdatasourceaspect
@slf4j
@aspect
@order(-1)
@component
public class dynamicdatasourceaspect {
/**
* 切换数据源
*
* @param point
* @param datasource
*/
@before("@annotation(datasource))")
public void switchdatasource(joinpoint point, datasource datasource) {
if (!dynamicdatasourcecontextholder.containdatasourcekey(datasource.value().tostring())) {
log.info("datasource [{}] doesn't exist, use default datasource", datasource.value());
} else {
// 切换数据源
dynamicdatasourcecontextholder.setdatasourcekey(datasource.value().tostring());
log.info("switch datasource to [{}] in method [{}]", dynamicdatasourcecontextholder.getdatasourcekey(),
point.getsignature());
}
}
/**
* 重置数据源
*
* @param point
* @param datasource
*/
@after("@annotation(datasource))")
public void restoredatasource(joinpoint point, datasource datasource) {
// 将数据源置为默认数据源
dynamicdatasourcecontextholder.cleardatasourcekey();
log.info("restore datasource to [{}] in method [{}]", dynamicdatasourcecontextholder.getdatasourcekey(), point.getsignature());
}
}
如何使用
@override
@datasource(datasourceenum.db1)
public page<audittaskdto> queryaudittask(audittaskquery query) {
page<audittaskdto> page = basemapper.queryaudittask(query);
return page;
}
如此就可以直接使用自定义的@datasource注解来切换数据源啦~~~
以上就是springboot配置动态数据源的实战详解的详细内容,更多关于springboot动态数据源的资料请关注代码网其它相关文章!
发表评论