数据源切换方法
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动态数据源的资料请关注代码网其它相关文章!
发表评论