您好,欢迎访问一九零五行业门户网

SpringBoot基于AbstractRoutingDataSource如何实现多数据源动态切换

一、场景在生产业务中,有一些任务执行了耗时较长的查询操作,在实时性要求不高的时候,我们希望将这些查询sql分离出来,去从库查询,以减少应用对主数据库的压力。
一种方案是在配置文件中配置多个数据源,然后通过配置类来获取数据源以及mapper相关的扫描配置,不同的数据源配置不佟的mapper扫描位置,然后需要哪一个数据源就注入哪一个mapper接口即可,这种方法比较简单。特征是通过mapper扫描位置区分数据源。
一种可行的方案是事先设置一个默认的数据源,同时定义多个其他的数据源,利用aop实现注解方式来切换数据源。对abstractroutingdatasource类进行继承是实现这种方案的关键。这是本文的重点。
二、原理abstractroutingdatasource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 abstractroutingdatasource 动态织入到程序中,灵活的进行数据源切换。
基于abstractroutingdatasource的多数据源动态切换,可以实现读写分离。逻辑如下:
/** * retrieve the current target datasource. determines the * {@link #determinecurrentlookupkey() current lookup key}, performs * a lookup in the {@link #settargetdatasources targetdatasources} map, * falls back to the specified * {@link #setdefaulttargetdatasource default target datasource} if necessary. * @see #determinecurrentlookupkey() */ protected datasource determinetargetdatasource() { assert.notnull(this.resolveddatasources, "datasource router not initialized"); object lookupkey = determinecurrentlookupkey(); datasource datasource = this.resolveddatasources.get(lookupkey); if (datasource == null && (this.lenientfallback || lookupkey == null)) { datasource = this.resolveddefaultdatasource; } if (datasource == null) { throw new illegalstateexception("cannot determine target datasource for lookup key [" + lookupkey + "]"); } return datasource; }/** * determine the current lookup key. this will typically be * implemented to check a thread-bound transaction context. * <p>allows for arbitrary keys. the returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolvespecifiedlookupkey} method. */ @nullable protected abstract object determinecurrentlookupkey();
通过实现抽象方法determinecurrentlookupkey指定需要切换的数据源
三、代码示例示例中主要依赖
com.alibaba.druid;tk.mybatis
定义一个类用于关联数据源。通过 theadlocal 来保存每个线程选择哪个数据源的标志(key)
@slf4jpublic class dynamicdatasourcecontextholder { private static final threadlocal<string> contextholder = new threadlocal<string>(); public static list<string> datasourceids = new arraylist<string>(); public static void setdatasourcetype(string datasourcetype) { log.info("设置当前数据源为{}",datasourcetype); contextholder.set(datasourcetype); } public static string getdatasourcetype() { return contextholder.get() ; } public static void cleardatasourcetype() { contextholder.remove(); } public static boolean containsdatasource(string datasourceid){ log.info("list = {},dataid={}", json.tojson(datasourceids),datasourceid); return datasourceids.contains(datasourceid); }}
继承
abstractroutingdatasource
public class dynamicdatasource extends abstractroutingdatasource { @override protected object determinecurrentlookupkey() { return dynamicdatasourcecontextholder.getdatasourcetype(); }}
配置主数据库master 与从数据库slave(略)。数据源配置可以从简
@configuration@tk.mybatis.spring.annotation.mapperscan(value = {"com.server.dal.dao"})@conditionalonproperty(name = "java.druid.datasource.master.url")public class javadruiddatasourceconfiguration { private static final logger logger = loggerfactory.getlogger(javadruiddatasourceconfiguration.class); @resource private javadruiddatasourceproperties druiddatasourceproperties; @primary @bean(name = "masterdatasource", initmethod = "init", destroymethod = "close") @conditionalonmissingbean(name = "masterdatasource") public druiddatasource javareaddruiddatasource() { druiddatasource result = new druiddatasource(); try {// result.setname(druiddatasourceproperties.getname()); result.seturl(druiddatasourceproperties.geturl()); result.setusername(druiddatasourceproperties.getusername()); result.setpassword(druiddatasourceproperties.getpassword()); result.setconnectionproperties( "config.decrypt=false;config.decrypt.key=" + druiddatasourceproperties.getpwdpublickey()); result.setfilters("config"); result.setmaxactive(druiddatasourceproperties.getmaxactive()); result.setinitialsize(druiddatasourceproperties.getinitialsize()); result.setmaxwait(druiddatasourceproperties.getmaxwait()); result.setminidle(druiddatasourceproperties.getminidle()); result.settimebetweenevictionrunsmillis(druiddatasourceproperties.gettimebetweenevictionrunsmillis()); result.setminevictableidletimemillis(druiddatasourceproperties.getminevictableidletimemillis()); result.setvalidationquery(druiddatasourceproperties.getvalidationquery()); result.settestwhileidle(druiddatasourceproperties.istestwhileidle()); result.settestonborrow(druiddatasourceproperties.istestonborrow()); result.settestonreturn(druiddatasourceproperties.istestonreturn()); result.setpoolpreparedstatements(druiddatasourceproperties.ispoolpreparedstatements()); result.setmaxopenpreparedstatements(druiddatasourceproperties.getmaxopenpreparedstatements()); if (druiddatasourceproperties.isenablemonitor()) { statfilter filter = new statfilter(); filter.setlogslowsql(druiddatasourceproperties.islogslowsql()); filter.setmergesql(druiddatasourceproperties.ismergesql()); filter.setslowsqlmillis(druiddatasourceproperties.getslowsqlmillis()); list<filter> list = new arraylist<>(); list.add(filter); result.setproxyfilters(list); } } catch (exception e) { logger.error("数据源加载失败:", e); } finally { result.close(); } return result; }}
注意主从数据库的bean name
配置dynamicdatasource
targetdatasources 存放数据源的k-v对
defaulttargetdatasource 存放默认数据源
配置事务管理器和sqlsessionfactorybean
@configurationpublic class dynamicdatasourceconfig { private static final string mapper_location = "classpath*:sqlmap/dao/*mapper.xml"; @bean(name = "dynamicdatasource") public dynamicdatasource dynamicdatasource(@qualifier("masterdatasource") druiddatasource masterdatasource, @qualifier("slavedatasource") druiddatasource slavedatasource) { map<object, object> targetdatasource = new hashmap<>(); dynamicdatasourcecontextholder.datasourceids.add("masterdatasource"); targetdatasource.put("masterdatasource", masterdatasource); dynamicdatasourcecontextholder.datasourceids.add("slavedatasource"); targetdatasource.put("slavedatasource", slavedatasource); dynamicdatasource datasource = new dynamicdatasource(); datasource.settargetdatasources(targetdatasource); datasource.setdefaulttargetdatasource(masterdatasource); return datasource; } @primary @bean(name = "javatransactionmanager") @conditionalonmissingbean(name = "javatransactionmanager") public datasourcetransactionmanager transactionmanager(@qualifier("dynamicdatasource") dynamicdatasource druiddatasource) { return new datasourcetransactionmanager(druiddatasource); } @bean(name = "sqlsessionfactorybean") public sqlsessionfactorybean mygetsqlsessionfactory(@qualifier("dynamicdatasource") dynamicdatasource datasource) { sqlsessionfactorybean sqlsessionfactorybean = new sqlsessionfactorybean(); resourcepatternresolver resolver = new pathmatchingresourcepatternresolver(); try { sqlsessionfactorybean.setmapperlocations(resolver.getresources(mapper_location)); } catch (ioexception e) { e.printstacktrace(); } sqlsessionfactorybean.setdatasource(datasource); return sqlsessionfactorybean; }}
定义一个注解用于指定数据源
@target({ elementtype.method, elementtype.type })@retention(retentionpolicy.runtime)@documentedpublic @interface targetdatasource { string value();}
切面的业务逻辑。注意指定order,以确保在开启事务之前执行 。
@aspect@slf4j@order(-1)@componentpublic class datasourceaop { @before("@annotation(targetdatasource)") public void changedatasource(joinpoint point, targetdatasource targetdatasource) { string dsid = targetdatasource.value(); if (!dynamicdatasourcecontextholder.containsdatasource(dsid)) { log.error("数据源[{}]不存在,使用默认数据源 > {}" + targetdatasource.value() + point.getsignature()); } else { log.info("usedatasource : {} > {}" + targetdatasource.value() + point.getsignature()); dynamicdatasourcecontextholder.setdatasourcetype(targetdatasource.value()); } } @after("@annotation(targetdatasource)") public void restoredatasource(joinpoint point, targetdatasource targetdatasource) { log.info("revertdatasource : {} > {}"+targetdatasource.value()+point.getsignature()); dynamicdatasourcecontextholder.cleardatasourcetype(); }}
以上略去了pom.xml和application.yml
使用示例
@resource private shopbilldomapper shopbilldomapper; //使用默认数据源 public shopbillbo querytestdata(integer id){ return shopbilldomapper.getbyshopbillid(id); } //切换到指定的数据源 @targetdatasource("slavedatasource") public shopbill querytestdata2(integer id){ return shopbilldomapper.getbyshopbillid(id); }
如果返回不同的结果就成功了!
以上就是springboot基于abstractroutingdatasource如何实现多数据源动态切换的详细内容。
其它类似信息

推荐信息