大家好,我是入错行的bug猫。(http://blog.csdn.net/qq_41399429,谢绝转载)
在Springmvc、Springboot中配置多个数据源,相信各位老师,都司空见惯、手到擒来、炒鸡煎蛋了! ᕙ( * •̀ ᗜ •́ * )ᕗ
但是,数据源one
和数据源two
,需要处于同一个事务,要肿么办?
就是A数据库对应数据源one
,B数据库对应数据源two
,改了A库一张表数据,同时改B库另外一张表,两个事件,要在同一个事务中执行,要如何处理?
( ̄▽ ̄)ノ:先森,藕知道,用分布式事务!
( •̀_•́ ) 凸 :秀儿,先坐下!
如果系统本身就是分布式应用,使用使用分布式事务,无可厚非,用就用呗。
但是,如果只是一个普通的单机应用,先别说能不能驾驭得住分布式事务,用起来感觉就像是坦克拖着板车,庭院的花盆里长了点杂草,却弄了一辆无人机来洒农药! 0v0
问题的确能解决,但是,太重了。
咱们聊聊单机应用中,如何方便、快捷、轻量级的让多个数据源在同一个事务中
以SpringMVC为例,先看看数据源、事务的配置
<bean id="onedataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- one的数据库配置,获得one数据源 -->
</bean>
<!-- 通过数据源,得到数据库会话工厂 -->
<bean id="onesqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="onedataSource" />
<property name="mapperLocations" value="classpath:mysql/one/*.xml"></property>
<property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean id="oneconfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bugcat.nobug.one.dao" />
<property name="sqlSessionFactoryBeanName" value="onesqlSessionFactory"></property>
</bean>
<!-- 事务管理 -->
<bean id="onetx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="oneconfigurer" />
</bean>
<!-- 使用注解开启事务,事务管理器为 onetx -->
<tx:annotation-driven transaction-manager="onetx" proxy-target-class="true" />
<!-- 第二个数据源配置 -->
<bean id="twodataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- two的数据库配置,获得two数据源 -->
</bean>
<bean id="twosqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="twodataSource" />
<property name="mapperLocations" value="classpath:mysql/two/*.xml"></property>
<property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>
<bean id="twoconfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bugcat.nobug.two.dao" />
<property name="sqlSessionFactoryBeanName" value="twosqlSessionFactory"></property>
</bean>
<bean id="twotx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="twoconfigurer" />
</bean>
one数据源
和two数据源
,配置对仗得非常工整。就是one多了一个<tx:annotation-driven transaction-manager="onetx" proxy-target-class="true" />
。画重点,在系统中使用@Transactional
,默认对应的事务管理器是 onetx
,数据源是 onedataSource
!
two数据源
也有事务管理器,只不过在使用@Transactional
时,还必须指定value
值。
不要把注解看得太神奇!千万别以为@xxxx
是一种很高深的java语法,加了它之后可以做任何事!
bean拷贝,有2个属性名有点不一样,写个自定义注解上,咦?怎么不生效?!为母事不生效??
当然不生效,因为注解是有了,但是,处理属性名的业务还没有呢,不告诉程序怎么处理,程序怎么知道要怎么处理?靠意识控制么…
所以,其实在Spring的jar包中,还有针对@Transactional
注解,处理的业务逻辑代码。再次强调,不要以为 (ಥ_ಥ)@Transactional
是一种语法、java关键词之类的东西
其实就是搞了一个切面,在方法执行前,开启事务;在方法执行后,提交事务;异常后,回滚事务
要保证数据源one
与数据库two
在同一个事务,可以理解成:事务管理器onetx
开启事务,同时事务管理器twotx
也开启事务;onetx
提交,twotx
也提交;onetx
回滚了,twotx
也跟着回滚!
( ̄▽ ̄)ノ:先森,藕又知道了,这是要改transaction jar包源码的节奏?!
( •̀_•́ ) 凸 :秀儿,你还是先坐下!
Spring为@Transactional
做了一个切面,为毛我们不能为自定义的注解,也搞一个切面?
╭( ′• o •′ )╭☞ 这能叫抄袭么,程序猿的事,只能算借鉴…
这个切面需要做的事情:
- 接收需要组合在一起的数据源对应的事务管理器id
- 在执行业务方法之前,通过这些事务管理器,手动开启事务
- 执行业务方法
- 业务方法执行完毕之后,再使用这些事务管理器,手动提交事务
- 如果业务方法抛出异常:判断是否不需要回滚事务,如果不需要回滚,则提交事务;否则回滚
先来个自定义注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* 多数据源,组合事务
*
*
* @author bugcat
* */
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TransactionalGroup {
/**
*
* 配置的事务id
*
*
* <bean id="onetx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
* <property name="dataSource" ref="onetxdataSource" />
* </bean>
*
*
* <p> 在方法上添加:@TransactionalGroup("onetx","twotx") </p>
*
* */
String[] value();
/******* 以下参数,参见 @Transactional ********/
//事务传播行为
Propagation propagation() default Propagation.REQUIRED;
//事务隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 哪些异常不会滚事务
Class<? extends Throwable>[] noRollbackFor() default {};
}
自定义切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 切面
* @author bugcat
* */
@Aspect
@Component
public class Pointcuts {
/**
* 多数据源组合事务
* 手动开启一个事物
* */
@Pointcut("@annotation(com.bugcat.nobug.annotation.TransactionalGroup)")
public void transactionalGroupAspect() {};
@Around(value = "transactionalGroupAspect()")
public Object transactionalGroupAspectArround(ProceedingJoinPoint pjp) throws Throwable {
Object obj = null;
boolean doit = false;//是否执行了方法
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method targetMethod = methodSignature.getMethod();
List<TransactionParam> tsParams = null;
TransactionalGroup groupAnn = targetMethod.getAnnotation(TransactionalGroup.class);
if( groupAnn != null ){
Isolation isolation = groupAnn.isolation(); //事务隔离级别
Propagation propagation = groupAnn.propagation(); //传播行为
String[] groups = groupAnn.value();
if( groups.length > 0 ){
doit = true; //执行 pjp.proceed 的if分支
Set<String> noRollbackForNameSet = new HashSet<>();
Class<? extends Throwable>[] noRollbackFor = groupAnn.noRollbackFor();
if( noRollbackFor != null && noRollbackFor.length > 0 ){
for ( Class<? extends Throwable> clazz : noRollbackFor ) {
noRollbackForNameSet.add(clazz.getSimpleName().toLowerCase());
}
}
tsParams = new ArrayList<>(groups.length);
try {
for (String tx : groups ) {
TransactionParam tsParam = new TransactionParam();
//获取到事务服务类,SpringContextUtil 为Spring容器
tsParam.tm = SpringContextUtil.getBean(tx, DataSourceTransactionManager.class);
tsParam.def = new DefaultTransactionDefinition();
// 事物隔离级别
tsParam.def.setIsolationLevel(isolation.value());
// 事物传播行为
tsParam.def.setPropagationBehavior(propagation.value());
// 获得事务状态
tsParam.status = tsParam.tm.getTransaction(tsParam.def);
tsParams.add(tsParam);
}
obj = pjp.proceed();
//提交事务
for(TransactionParam tsParam : tsParams){
tsParam.tm.commit(tsParam.status);
}
} catch ( Throwable e ) {
boolean rollback = true;//事务回滚
String errName = e.getClass().getName().toLowerCase();
if( "transactionexception".equals(errName) ){ // 这是sql异常,救不了了
rollback = true;
} else {
if( noRollbackForNameSet.contains(errName) ){ //发生异常,不回滚
rollback = false;
}
}
if( !rollback ){ //事务不回滚
try {
for(TransactionParam tsParam : tsParams){
tsParam.tm.commit(tsParam.status);
}
throw e; //但是还是要抛出异常
// end
} catch ( org.springframework.transaction.TransactionException e1 ) {//如果这步还出现sql异常,只能回滚事务了
e1.printStackTrace();
}
}
//事务回滚
for(TransactionParam tsParam : tsParams){
tsParam.tm.rollback(tsParam.status);
}
throw e;
}
}
}
if( !doit ){
obj = pjp.proceed();
}
return obj;
}
}
class TransactionParam{
//事务服务类
DataSourceTransactionManager tm;
//默认事务对象
DefaultTransactionDefinition def;
//事务状态
TransactionStatus status;
}
最后的呆毛
@TransactionalGroup({"onetx", "twotx"})
public void save(Demo demo) {
oneDemoDao.updateById(demo);
twoDemoDao.updateById(demo);
}
注意,如果@TransactionalGroup
中包含了默认事务处理器的id,那么在业务方法上、业务方法类上,千万别再加@Transactional
注解了!!!
( ̄▽ ̄)ノ:先森,介个藕知道,@TransactionalGroup
的切面中,通过事务管理器id=“onetx”,获取到数据源one的事务管理器,手动开启了它的事务。如果再在方法上添加@Transactional
,会导致Spring又会开启一个事务!
( ̄ε(# ̄)☆o=(-`д´- 。) :就你话多!
于是一个mini版的组合事务搞定了,可喜可贺可喜可贺