详解基于spring多数据源动态调用及其事务处理
需求:
有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个。即同时保持多个数据库的连接,在方法中根据传入的参数来确定。
下图的单数据源的调用和多数据源动态调用的流程,可以看出在dao层中需要有一个datasource选择器,来确定到底是调用哪个数据源。
实现方式
对dao层提供一个公共父类,保持有多个数据源的连接(本人是基于ibatis,即保持多个sqlsessiontemplate)
/** * created by hzlizhou on 2017/2/6. */ public abstract class multidatasourcedao implements idaosupport { private map<string, sqlsessiontemplate> sqlsessiontemplatemap; private multidatasourceselector multidatasourceselector; public multidatasourcedao(map<string, sqlsessiontemplate> sqlsessiontemplatemap, multidatasourceselector multidatasourceselector) { this.sqlsessiontemplatemap = sqlsessiontemplatemap; this.multidatasourceselector = multidatasourceselector; } public map<string, sqlsessiontemplate> getsqlsessiontemplatemap() { return sqlsessiontemplatemap; } public void setsqlsessiontemplatemap(map<string, sqlsessiontemplate> sqlsessiontemplatemap) { this.sqlsessiontemplatemap = sqlsessiontemplatemap; } //子类通过这个方法动态获取sqlsessiontemplate protected sqlsessiontemplate getsqlsessiontemplate() { string clustername = multidatasourceselector.getname(); sqlsessiontemplate result = sqlsessiontemplatemap.get(clustername); assert.notnull(result); return result; } }
multidatasourceselector是一个借口,根据当前的调用环境,返回不不同的参数,根据这个参数就可以确定使用哪一个sqlsessiontemplate,例如我是把当前环境放入到threadlocal中的
public interface multidatasourceselector { string getname(); } public class dubbocontextdatasourceselector implements multidatasourceselector { private string defaultname; public dubbocontextdatasourceselector(string defaultname) { this.defaultname = defaultname; } @override public string getname() { //dubbocontextholder 是一个保持一个threadlocal的map string res = dubbocontextholder.getcontext().get(dubbocontextconstants.cluster_name); if (res == null) { res = getdefaultname(); } return res; } public string getdefaultname() { return defaultname; } }
然后在dao层的中获取sqlsessiontemplate的时候就是动态了。
动态事务
其实这个都好办,然后我们就面临一个稍微复杂一点的问题,那datasource是动态的,事务也就必须是动态了的。而且还对原有的代码没有侵入(例如spring中的@transactional 注解),那实现一个类似@transactional的方法吧。名字就叫做@dynamictransactional
@documented @target({method, type}) @retention(runtime) public @interface dynamictransactional { propagation propagation() default propagation.required; class<? extends throwable>[] rollbackfor() default {}; }
基本思想是在通过aop切面拦截@dynamictransactional注解,调用,然后自己编程实现事务
切面内的核心方法是
private object invokewithintransaction(final proceedingjoinpoint pjp, final dynamictransactional dynamictransaction) { //创建transactiontemplate final platformtransactionmanager tran = multitransactionmanagerholder.gettransactionmanager(); transactiontemplate transactiontemplate = new transactiontemplate(); transactiontemplate.setpropagationbehavior(dynamictransaction.propagation().value()); transactiontemplate.settransactionmanager(tran); //在事务中执行 return transactiontemplate.execute(new transactioncallback<object>() { @override public object dointransaction(transactionstatus status) { object result = null; try { result = pjp.proceed(); } catch (throwable throwable) { class<? extends throwable>[] c = dynamictransaction.rollbackfor(); for (class<? extends throwable> tmp : c) { if (tmp.isassignablefrom(throwable.getclass())) { status.setrollbackonly(); } } } return result; } }); }
其中multitransactionmanagerholder和上面动态数据源选择的原理一样,通过从threadlocal中拿去变量,选择对应的transactionmanager返回
切面的配置:重点是怎么对指定注解进行切面
<aop:config> <aop:aspect id="multitransactionmanageraspect" ref="multitransactionmanageraop"> <aop:around method="invokewithintransaction" arg-names="dynamictransaction" pointcut="@annotation(dynamictransaction)"/> </aop:aspect> </aop:config>
当然,这里只是实现了在方法上的@dynamictransactional使用,如果该注解还要对类使用,对所有函数加一个切点,判断该切点的类上是否有@dynamictransactional注解
注意:由于切面的优先级,如果要实现 方法上的注解优先级高于类上的,还需要一点点小的处理
调用时序图
自己实现基于abstractroutingdatasource,把多个datasource加入到sqlsessionfactory,和之前的方式一样,通过threadlocal来确定使用哪个datasource。
关于动态事务,上面是使用切面,自定义标签,使用transactiontemplate来实现的,如果想更优雅的话,可以仿照datasourcetransactionmanager写一个,
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。