详解基于spring多数据源动态调用及其事务处理

时间:2021-09-06 13:26:37

需求:

有些时候,我们需要连接多个数据库,但是,在方法调用前并不知道到底是调用哪个。即同时保持多个数据库的连接,在方法中根据传入的参数来确定。

下图的单数据源的调用和多数据源动态调用的流程,可以看出在dao层中需要有一个datasource选择器,来确定到底是调用哪个数据源。

详解基于spring多数据源动态调用及其事务处理

实现方式

对dao层提供一个公共父类,保持有多个数据源的连接(本人是基于ibatis,即保持多个sqlsessiontemplate)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
 
 * 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中的

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@documented
 
@target({method, type})
 
@retention(runtime)
 
public @interface dynamictransactional {
 
 
 
  propagation propagation() default propagation.required;
 
 
 
  class<? extends throwable>[] rollbackfor() default {};
 
}

基本思想是在通过aop切面拦截@dynamictransactional注解,调用,然后自己编程实现事务

详解基于spring多数据源动态调用及其事务处理

切面内的核心方法是

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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返回

 切面的配置:重点是怎么对指定注解进行切面

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<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。

详解基于spring多数据源动态调用及其事务处理

关于动态事务,上面是使用切面,自定义标签,使用transactiontemplate来实现的,如果想更优雅的话,可以仿照datasourcetransactionmanager写一个,

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://www.cnblogs.com/lizo/p/7089263.html?utm_source=tuicool&utm_medium=referral