java多数据源回滚_Spring多数据源事务

前言

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

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

总体思路

网上已经有很多关于jta-atomikos的相关文章,本文可能有点绕,不容易看得懂,所以在此描述一下思路:1、配置mybatis以及druid使得其能够实现连接多个数据源。 2、通过自定义数据源,将多个数据源的事务整合成一个SqlSession,进而实现统一管理事务。 3、利用AOP以及自定义注解实现动态的切换数据源(即是A的dao应该连接A的数据源。)。

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

添加依赖

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

org.springframework.boot

spring-boot-starter-aop

org.springframework.boot

spring-boot-starter-jta-atomikos

配置多个数据源

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();

}

}

参考文章

最后

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值