Spring多数据源事务

标签: spring 数据 | 发表时间:2020-01-08 15:00 | 作者:xbmchina
出处:https://juejin.im/welcome/backend

前言

接着上一篇文章 Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚。这里也是使用大家广泛使用的 jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用。

如果你非常着急,那么可以直接下载这个项目看看即可:

github.com/xbmchina/mu…

总体思路

网上已经有很多关于jta-atomikos的相关文章,本文可能有点绕,不容易看得懂,所以在此描述一下思路:

1、配置mybatis以及druid使得其能够实现连接多个数据源。 2、通过自定义数据源,将多个数据源的事务整合成一个SqlSession,进而实现统一管理事务。 3、利用AOP以及自定义注解实现动态的切换数据源(即是A的dao应该连接A的数据源。)。

更多详细了解可以查看源码,或者下面的简单介绍。

添加依赖

主要依赖就是jta-atomikos,其余的mybatis与druid的相关依赖就不粘贴了。

  <dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jta-atomikosartifactId>
dependency>
复制代码

配置多个数据源

1、首先,定义一个枚举来说明一下当前数据源实例key有哪些。

  public class DataSourceKey {
    /** 数据库源one*/
    public static final String ONE= "one";

    /** 数据库源two*/
    public static final String TWO= "two";
}
复制代码

2、其次,使用ThreadLocal存储当前使用数据源实例的key。ThreadLocal实例化的时候给一个master的默认值,也就是默认数据源是master数据源。

  public class DynamicDataSourceContextHolder {

private static ThreadLocal   CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.MASTER.getName());

public static List   dataSourceKeys = new ArrayList  ();

public static void setDataSourceKey(String key){
CONTEXT_HOLDER.set(key);
}

public static Object getDataSourceKey(){
return CONTEXT_HOLDER.get();
}

public static void clearDataSourceKey(){
CONTEXT_HOLDER.remove();
}

public static Boolean containDataSourceKey(String key){
return dataSourceKeys.contains(key);
}

}
复制代码

3、重写AbstractRoutingDataSource的determineCurrentLookupKey方法,在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key。

  
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 取得当前使用那个数据源。
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDatasourceType();
    }

}
复制代码

4、通过SqlSessionFactory 重新组装整合多个数据源,最终返回sqlSessionTemplate给到dao层。

  
@Configuration
@MapperScan(basePackages = MyBatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig extends AbstractDataSourceConfig {

    //mapper模式下的接口层
    static final String BASE_PACKAGE = "cn.xbmchina.multidatasourceatomikos.mapper";

    //对接数据库的实体层
    static final String ALIASES_PACKAGE = "ccn.xbmchina.multidatasourceatomikos.domain";

    static final String MAPPER_LOCATION = "classpath:mapper/*.xml";


    @Primary
    @Bean(name = "dataSourceOne")
    public DataSource dataSourceOne(Environment env) {
        String prefix = "spring.datasource.druid.one.";
        return getDataSource(env,prefix,"one");
    }

    @Bean(name = "dataSourceTwo")
    public DataSource dataSourceTwo(Environment env) {
        String prefix = "spring.datasource.druid.two.";
        return getDataSource(env,prefix,"two");
    }



    @Bean("dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("dataSourceOne")DataSource dataSourceOne, @Qualifier("dataSourceTwo")DataSource dataSourceTwo) {
        Map   targetDataSources = new HashMap<>();
        targetDataSources.put("one",dataSourceOne);
        targetDataSources.put("two",dataSourceTwo);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(dataSourceOne);
        return dataSource;
    }

    @Bean(name = "sqlSessionFactoryOne")
    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource dataSource)
            throws Exception {
        return createSqlSessionFactory(dataSource);
    }

    @Bean(name = "sqlSessionFactoryTwo")
    public SqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource dataSource)
            throws Exception {
        return createSqlSessionFactory(dataSource);
    }




    @Bean(name = "sqlSessionTemplate")
    public CustomSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryOne")SqlSessionFactory factoryOne, @Qualifier("sqlSessionFactoryTwo")SqlSessionFactory factoryTwo) throws Exception {
        Map   sqlSessionFactoryMap = new HashMap<>();
        sqlSessionFactoryMap.put("one",factoryOne);
        sqlSessionFactoryMap.put("two",factoryTwo);

        CustomSqlSessionTemplate customSqlSessionTemplate = new CustomSqlSessionTemplate(factoryOne);
        customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
        return customSqlSessionTemplate;
    }

    /**
     * 创建数据源
     * @param dataSource
     * @return
     */
    private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception{
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setVfs(SpringBootVFS.class);
        bean.setTypeAliasesPackage(ALIASES_PACKAGE);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
        return bean.getObject();
    }
}

复制代码

5、使用AOP,以自定义注解注解在的方法为切点,动态切换数据源

  
import cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource;
import cn.xbmchina.multidatasourceatomikos.db.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.lang.reflect.Method;

public class DataSourceAspect {
    protected static final ThreadLocal preDatasourceHolder = new ThreadLocal<>();

    /**
     * @param clazz
     * @param name
     * @return
     */
    private static Method findUniqueMethod(Class clazz, String name) {
        Class searchType = clazz;
        while (searchType != null) {
            Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
            for (Method method : methods) {
                if (name.equals(method.getName())) {
                    return method;
                }
            }
            searchType = searchType.getSuperclass();
        }
        return null;
    }

    @Pointcut("@annotation(cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource)")
    protected void datasourceAspect() {

    }

    /**
     * 根据@TargetDataSource的属性值设置不同的dataSourceKey,以供DynamicDataSource
     */
    @Before("datasourceAspect()")
    public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {
        String key = determineDatasource(jp);
        if (key == null) {
            DataSourceContextHolder.setDatasourceType(null);
            return;
        }
        preDatasourceHolder.set(DataSourceContextHolder.getDatasourceType());
        DataSourceContextHolder.setDatasourceType(key);

    }

    /**
     * @param jp
     * @return
     */
    public String determineDatasource(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        Class targetClass = jp.getSignature().getDeclaringType();
        String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);
        String dataSourceForTargetMethod = resolveDataSourceFromMethod(targetClass, methodName);
        String resultDS = determinateDataSource(dataSourceForTargetClass, dataSourceForTargetMethod);
        return resultDS;
    }

    /**
     *
     */
    @After("datasourceAspect()")
    public void restoreDataSourceAfterMethodExecution() {
        DataSourceContextHolder.setDatasourceType(preDatasourceHolder.get());
        preDatasourceHolder.remove();
    }

    /**
     * @param targetClass
     * @param methodName
     * @return
     */
    private String resolveDataSourceFromMethod(Class targetClass, String methodName) {
        Method m = findUniqueMethod(targetClass, methodName);
        if (m != null) {
            TargetDataSource choDs = m.getAnnotation(TargetDataSource.class);
            return resolveDataSourceName(choDs);
        }
        return null;
    }

    /**
     * @param classDS
     * @param methodDS
     * @return
     */
    private String determinateDataSource(String classDS, String methodDS) {
        return methodDS == null ? classDS : methodDS;
    }

    /**
     * @param targetClass
     * @return
     */
    private String resolveDataSourceFromClass(Class targetClass) {
        TargetDataSource classAnnotation = (TargetDataSource) targetClass.getAnnotation(TargetDataSource.class);
        return null != classAnnotation ? resolveDataSourceName(classAnnotation) : null;
    }

    /**
     * @param ds
     * @return
     */
    private String resolveDataSourceName(TargetDataSource ds) {
        return ds == null ? null : ds.value();
    }
}
复制代码

参考文章

blog.csdn.net/WayneLee080…

最后

更多文章可关注公众号**【爱编码】 ,回复2020**有实战视频资料哦。

相关 [spring 数据] 推荐:

Spring配置多数据源

- - ITeye博客
首先在配置文件中配置多个dataSource. 扩展Spring的AbstractRoutingDataSource抽象类,实现动态数据源. AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心.这里对该方法进行Override.

Spring多数据源事务

- - 掘金后端
接着上一篇文章 Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚. 这里也是使用大家广泛使用的 jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用. 如果你非常着急,那么可以直接下载这个项目看看即可:. 网上已经有很多关于jta-atomikos的相关文章,本文可能有点绕,不容易看得懂,所以在此描述一下思路:.

spring+hibernate多数据源的应用

- - CSDN博客推荐文章
我有两个数据库test,和test1,两个库里都有一张表TEST_ONE. applicationContext.xml的配置如下. //数据库test1配置.   //整合两个数据源,指定数据源管理类.    //数据库test.    //数据库test1. //这个类是用来管理数据源的,配置文件中.

Spring+MyBatis实践——MyBatis访问数据库

- - 开源软件 - ITeye博客
    在http://dufengx201406163237.iteye.com/blog/2102054中描述了工程的配置,在此记录一下如何使用MyBatis访问数据库;. . .

spring实现数据库读写分离

- - 行业应用 - ITeye博客
现在大型的电子商务系统,在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库. Master库负责数据更新和 实时数据查询,Slave库当然负责非实时数据查询. 因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据 通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验.

Spring多数据源的配置

- - 编程语言 - ITeye博客
在大型的应用中,为了提高数据库的水平伸缩性,对多个数据库实例进行管理,需要配置多数据源. 在Spring框架被广泛运用的今天,可以很简单的运用Spring中的特性配置动态多数据. 首先配置一个基于c3p0.ComboPooledDataSource的数据源A. 接着扩展一个Spring提供的AbstractRoutingDataSource,Override 其中的 determineCurrentLookupKey方法实现数据源的route.

Spring Boot使用redis做数据缓存

- - ITeye博客
SysUser.class)); //请注意这里. 3 redis服务器配置. /** *此处的dao操作使用的是spring data jpa,使用@Cacheable可以在任意方法上,*比如@Service或者@Controller的方法上 */ public interface SysUserRepo1 extends CustomRepository {.

Spring MVC防止数据重复提交

- - 编程语言 - ITeye博客
来讲一下如何在Spring MVC里面解决此问题(其它框架也一样,逻辑一样,思想一样,和具体框架没什么关系). 要解决重复提交,有很多办法,比如说在提交完成后redirect一下,也可以用本文提到的使用token的方法(我不使用redirect是因为那样解决不了ajax提交数据或者移动应用提交数据,另一个原因是现在比较通行的方法是使用token,像python里的django框架也是使用token来解决).

spring数据源动态切换

- - 企业架构 - ITeye博客
     原文->http://exceptioneye.iteye.com/blog/1698064.       在Spring 2.0.1中引入了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上.

Spring AOP + Redis缓存数据库查询

- - 编程语言 - ITeye博客
我们希望能够将数据库查询结果缓存到Redis中,这样在第二次做同样的查询时便可以直接从redis取结果,从而减少数据库读写次数. 必须要做到与业务逻辑代码完全分离. 从缓存中读出的数据必须与数据库中的数据一致. 如何为一个数据库查询结果生成一个唯一的标识. Key),能唯一确定一个查询结果,同一个查询结果,一定能映射到同一个.