基于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写一个,

时间: 2024-10-12 20:06:46

基于spring多数据源动态调用及其事务处理的相关文章

springAOP实现基于注解的数据源动态切换

需求 代码实现读写数据库分离 武器 spring3.0以上版本 实现思路 1.继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,自定义数据源路由. 2.实现数据源类型管理工具,诸如DBContextHolder,包含设置和读取当前数据源配置. 3.实现数据源切换的AOP. 4.自定义只读注解,诸如@ReadOnlyKey. 5.配置transactionManager,实现aop. 代码示例 1.自定义的

基于spring的aop实现多数据源动态切换

https://lanjingling.github.io/2016/02/15/spring-aop-dynamicdatasource/ 基于spring的aop实现多数据源动态切换 发表于 2016-02-15   |   分类于 spring  | 一.多数据源动态切换原理 项目中我们经常会遇到多数据源的问题,尤其是数据同步或定时任务等项目更是如此:又例如:读写分离数据库配置的系统. 1.多数据源设置: 1)静态数据源切换:一般情况下,我们可以配置多个数据源,然后为每个数据源写一套对应的

基于 Spring 和 iBATIS 的动态可更新多数据源持久层

前言 我们时常会遇到一些 web 项目,需要从不同的数据源中抓取数据来进行分析,而这些数据源是有可能变化的,需要用户来进行动态的维护和添加.可是,大多数的 web 程序使用了应用服务器或者容器中间件来管理数据源的生命周期,因此数据源的变化自然不能够独立于程序,而需要由专业人士去进行维护,必要时还需要重新发布程序来适应数据源的变化,而且数据源的个数.数据库的类型也都会有所限制. 那么怎样才可以突破以上这些局限,彻底实现由用户远程对数据源进行维护和管理的需求呢?本文提出了一个有效的解决方案,该方案的

基于Struts2 Spring ibatis Oracle10g架构 多数据源动态切换实例

一.概述 基于Spring动态配置多数据源,在大型的应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效的提高系统的水平伸缩性,而这样的解决方案就会补同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求以及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据. Spring配置多个数据源的方式和具体使用过程,Spring对于多数据源,以数据库表为参照,大体上可以分为两大类情况: 1.表级上的跨数据库,即对于不同的数据库却有不相同的表(表名和表结构完全

基于Spring Boot,使用JPA动态调用Sql查询数据

在<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>,<基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合>完成了CRUD,调用存储过程查询数据. 很多复杂的情况下,会存在要直接执行SQL来获取数据. 通过“EntityManager”创建NativeQuery方法来执行动态SQL. 1.查询结果集映射 在包“com.kxh.example.demo.domain”下的“Contact”实体上编写命名的结果

基于Spring+Mybatis的多数据源动态切换

Spring单数据源直接在<bean id="dataSource">下配置数据源的各种连接参数.但动态数据源需要配置个各个数据源例如ds1.ds2等.然后在dataSource中动态根据传递过来的参数动态调用不同的数据源. 1.当进行访问时,首先通过DbContextHolder.setDbType("ds1");设置需要使用的数据源.DbContextHolder就是一个用来存储数据源信息的类,其中通过ThreadLocal来记录数据源信息. 2.D

基于spring+mybatis+atomikos+jta实现分布式事务(2)-动态切换数据源

本文介绍基于spring+mybatis+atomikos+jta实现分布式事务,由程序动态切换数据源,通过atomikos可实现分布式事务一致性. 版本:spring-3.2.9.RELEASE.mybatis-3.4.4.atomikos-4.0.5.jdk1.8 1,maven配置文件pom.xml如下: <!-- test --> <dependency> <groupId>junit</groupId> <artifactId>juni

基于xml的Spring多数据源配置和使用

上一篇讲了<基于注解的Spring多数据源配置和使用>,通过在类或者方法上添加@DataSource注解就可以指定某个数据源.这种方式的优点是控制粒度细,也更灵活. 缺点是当有些时候项目分模块开发,可能某一个模块只访问同一个数据源,这种场景下如果使用注解的话就要在这个模块下的所有dao接口都增加注解@DataSource,就会显得繁琐. 如果能直接在配置文件中一次性配置好,不用在每个类都添加注解岂不是更好?当然也是可以的^_^. 首先,再创建一个AOP切面,名为DataSourceAspect

基于注解的Spring多数据源配置和使用

前一段时间研究了一下spring多数据源的配置和使用,因为后期需要从多个数据源拉取数据,定时进行数据分析和报表统计.由于之前做过的项目都是单数据源的,没有遇到这种场景,所以也一直没有去了解过如何配置多数据源.后来发现其实基于spring来配置和使用多数据源还是比较简单的,因为spring框架已经预留了这样的接口可以方便数据源的切换.先看一下spring获取数据源的源码: 可以看到AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupK