Spring多数据源事务管理

大家好,我是入错行的bug猫。(http://blog.csdn.net/qq_41399429,谢绝转载)


在Springmvc、Springboot中配置多个数据源,相信各位老师,都司空见惯、手到擒来、炒鸡煎蛋了! ᕙ( * •̀ ᗜ •́ * )ᕗ


但是,数据源one数据源two,需要处于同一个事务,要肿么办?

就是A数据库对应数据源oneB数据库对应数据源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 •′ )╭☞ 这能叫抄袭么,程序猿的事,只能算借鉴…


这个切面需要做的事情:

  1. 接收需要组合在一起的数据源对应的事务管理器id
  2. 在执行业务方法之前,通过这些事务管理器,手动开启事务
  3. 执行业务方法
  4. 业务方法执行完毕之后,再使用这些事务管理器,手动提交事务
  5. 如果业务方法抛出异常:判断是否不需要回滚事务,如果不需要回滚,则提交事务;否则回滚



先来个自定义注解


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版的组合事务搞定了,可喜可贺可喜可贺





评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值