Spring多数据源事务
标签:
spring
数据
| 发表时间:2020-01-08 15:00 | 作者:xbmchina
出处:https://juejin.im/welcome/backend
前言
接着上一篇文章 Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚。这里也是使用大家广泛使用的 jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用。
如果你非常着急,那么可以直接下载这个项目看看即可:
总体思路
网上已经有很多关于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();
}
}
复制代码
参考文章
最后
更多文章可关注公众号**【爱编码】 ,回复2020**有实战视频资料哦。
相关 [spring 数据] 推荐:
首先在配置文件中配置多个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),能唯一确定一个查询结果,同一个查询结果,一定能映射到同一个.