Java注解实现动态数据源切换的实例代码
当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换。
实现原理
在spring 2.0.1中引入了abstractroutingdatasource, 该类充当了datasource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的datasource上。
看下abstractroutingdatasource:
public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean
abstractroutingdatasource继承了abstractdatasource,获取数据源部分:
/** * 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; }
抽象方法 determinecurrentlookupkey()
返回datasource的key值,然后根据这个key从resolveddatasources这个map里取出对应的datasource,如果找不到,则用默认的resolveddefaultdatasource。
我们要做的就是实现抽象方法 determinecurrentlookupkey()
返回数据源的key值。
使用方法
定义注解:
/** * created by huangyangquan on 2016/11/30. */ @retention(retentionpolicy.runtime) @target(elementtype.method) public @interface datasource { datasourcetype value(); }
注解为数据源的名称,可定义一个枚举类表示:
/** * created by huangyangquan on 2016/11/30. */ public enum datasourcetype { master, slave }
注解定义好了,我们利用spring的aop根据注解内容对数据源进行选择,这里需要利用上面提到的 abstractroutingdatasource
类,该类是能够实现数据源切换的关键所在。
定义类dynamicdatasource继承abstractroutingdatasource,并实现 determinecurrentlookupkey()
,返回数据源的key值。
/** * created by huangyangquan on 2016/11/30. */ public class dynamicdatasource extends abstractroutingdatasource { @override protected object determinecurrentlookupkey() { return dynamicdatasourceholder.getdatasourcetype(); } }
dynamicdatasourceholder
是我们管理datasource的类,将一次数据库操作的数据源名称保存在dynamicdatasourceholder中,以供后面的操作在此context中取数据源key,其中datasourcetype使用了线程本地变量来保证线程安全。
/** * created by huangyangquan on 2016/11/30. */ public class dynamicdatasourceholder { // 线程本地环境 private static final threadlocal<datasourcetype> contextholder = new threadlocal<datasourcetype>(); // 设置数据源类型 public static void setdatasourcetype(datasourcetype datasourcetype) { assert.notnull(datasourcetype, "datasourcetype cannot be null"); contextholder.set(datasourcetype); } // 获取数据源类型 public static datasourcetype getdatasourcetype() { return (datasourcetype) contextholder.get(); } // 清除数据源类型 public static void cleardatasourcetype() { contextholder.remove(); } }
我们在spring的配置文件中配置数据源key值得对应关系:
<bean id="spyghoteldatasource" class="com.aheizi.config.dynamicdatasource"> <property name="targetdatasources"> <map key-type="java.lang.string"> <entry key="master" value-ref="test-master-db"></entry> <entry key="slave" value-ref="test-slave-db"></entry> </map> </property> <property name="defaulttargetdatasource" ref="test-master-db"> </property> </bean>
设置targetdatasources和defaulttargetdatasource。 test-master-db
和 test-slave-db
表示主库的从库,是我们的两个数据源。
接下来配置aop切面:
<aop:aspectj-autoproxy proxy-target-class="false" /> <bean id="manydatasourceaspect" class="com.aheizi.config.datasourceaspect" /> <aop:config> <aop:aspect id="datasourcecut" ref="manydatasourceaspect"> <aop:pointcut expression="execution(* com.aheizi.dao.*.*(..))" id="datasourcecutpoint" /><!-- 配置切点 --> <aop:before pointcut-ref="datasourcecutpoint" method="before" /> </aop:aspect> </aop:config>
以下是切面中before执行的datasourceaspect的实现,主要实现的功能是获取方法上的注解,根据注解名称将值设置到dynamicdatasourceholder中,这样在执行查询的时候, determinecurrentlookupkey()
返回数据源的key值就是我们希望的那个数据源了。
/** * created by huangyangquan on 2016/11/30. */ public class datasourceaspect { private static final logger log = loggerfactory.getlogger(datasourceaspect.class); public void before(joinpoint point){ object target = point.gettarget(); string method = point.getsignature().getname(); class<?>[] classz = target.getclass().getinterfaces(); class<?>[] parametertypes = ((methodsignature) point.getsignature()).getmethod().getparametertypes(); try { method m = classz[0].getmethod(method, parametertypes); if (m != null && m.isannotationpresent(datasource.class)) { // 访问mapper中的注解 datasource data = m.getannotation(datasource.class); switch (data.value()) { case master: dynamicdatasourceholder.setdatasourcetype(datasourcetype.master); log.info("using datasource:{}", datasourcetype.master); break; case slave: dynamicdatasourceholder.setdatasourcetype(datasourcetype.slave); log.info("using datasource:{}", datasourcetype.slave); break; } } } catch (exception e) { log.error("datasource annotation error:{}", e.getmessage()); // 若出现异常,手动设为主库 dynamicdatasourceholder.setdatasourcetype(datasourcetype.master); } } }
这样我们就实现了一个动态数据源切换的功能。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。