Spring + Jta +JDBCTemplate 分布式事物实现方式

最近项目中需要用到多数据源管,数据访问层采用的是JDBCTemplate去做的,一开始是在数据源这块做了一个多数据源的操作类,通过拦截器注解去动态赋值指定的数据源操作,这种做法在查询中是没有问题的,但是DML操作时,会出现问题:事物中无法动态操作数据源,导致很多操作指针对第一个库。查询资料的时候发现:
DataSourceTransactionManager这个事物管理类只针对单个数据源进行事物控制.

解决的方案也有多种,这里只提及我实践过的两种:
  1. 开启多个数据源去进行操作,这种是可以自己去实现的.
  2. 利用JTA和Atomikos的多数据源分布事物管理

方案一:

思路:

  • 自定义一个注解类,通过传递参数告诉这个业务需要用到哪几个数据源
  • 然后仿照Spring中的@Transactional的实现模式,去构建一个集合来开启多个事物
  • 然后通过拦截器去动态分配业务给这个集合告诉他,要开几个事物

代码:
applicationContext-datasource.xml


 
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    
    
    
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    
    
    
    
        
    

    
        
    
    

applicationContext-service.xml 业务层配置文件




    
     多数据Aop配置 
    
        
        
            
            
        
    

注解类 :

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MultiTransactional {

 
    String[] values() default ""; 
    
    // TODO 当然你如果想做的更完善一点,可以参考@Transactional这个类,自己去做,什么传播机制啊,隔离级别啊 等等,思路是一样的
}

注解实现类 - 其实这里差不多都是沿用Spring的事物实现方式:

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.lang.reflect.Method;
import java.util.Stack;

/**
 * @Description: 多数据源动态管理
 * @Author: Liukx on 2017/7/28 - 16:41
 */
@Component("multiTransactionalAspect")
public class MultiTransactionalAspect {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private SpringUtils springUtils;

    /**
     * 切入点
     *
     * @param point
     * @return
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Stack dataSourceTransactionManagerStack = new Stack();
        Stack transactionStatuStack = new Stack();

        try {
            Object target = point.getTarget();
            String method = point.getSignature().getName();
            Class classz = target.getClass();
            Class[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
            Method m = classz.getMethod(method, parameterTypes);
            if (m != null && m.isAnnotationPresent(MultiTransactional.class)) {
                MultiTransactional multiTransactional = m.getAnnotation(MultiTransactional.class);
                if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, multiTransactional)) {
                    return null;
                }
                Object ret = point.proceed();
                commit(dataSourceTransactionManagerStack, transactionStatuStack);
                return ret;
            }
            Object ret = point.proceed();
            return ret;
        } catch (Exception e) {
            rollback(dataSourceTransactionManagerStack, transactionStatuStack);
            logger.error(String.format(
                    "MultiTransactionalAspect, method:%s-%s occors error:", point
                            .getTarget().getClass().getSimpleName(), point
                            .getSignature().getName()), e);
            throw e;
        }
    }

    /**
     * 打开一个事物方法
     *
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack
     * @param multiTransactional
     * @return
     */
    private boolean openTransaction(
            Stack dataSourceTransactionManagerStack,
            Stack transactionStatuStack,
            MultiTransactional multiTransactional) {
        // 获取注解中要打开的事物类型
        String[] transactionMangerNames = multiTransactional.values();
        if (ArrayUtils.isEmpty(multiTransactional.values())) {
            return false;
        }

        for (String beanName : transactionMangerNames) {
            // 创建一个新的事物管理器,用来管理接下来要用到的事物
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
            // 根据注解中获取到的数据标识,从spring容器中去查找对应的数据源
            DruidDataSource dataSource = (DruidDataSource) springUtils.getBean(beanName);
            //然后交给事物管理器去管理
            dataSourceTransactionManager.setDataSource(dataSource);
            // 定义一个新的事物定义
            DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
            // 设置一个默认的事物传播机制,注意的是这里可以拓展注解中没有用到的属性
            // defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
            // 将这个事物的定义放入Stack中
            /**
             * 其中为什么要用Stack来保存TransactionManager和TransactionStatus呢?
             * 那是因为Spring的事务处理是按照LIFO/stack behavior的方式进行的。
             * 如若顺序有误,则会报错:
             */
            transactionStatuStack.push(transactionStatus);
            dataSourceTransactionManagerStack
                    .push(dataSourceTransactionManager);
        }
        return true;
    }

    /**
     * 提交事物方法实现
     *
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack
     */
    private void commit(
            Stack dataSourceTransactionManagerStack,
            Stack transactionStatuStack) {
        while (!dataSourceTransactionManagerStack.isEmpty()) {
            dataSourceTransactionManagerStack.pop().commit(
                    transactionStatuStack.pop());
        }
    }

    /**
     * 回滚事物方法实现
     *
     * @param dataSourceTransactionManagerStack
     * @param transactionStatuStack
     */
    private void rollback(
            Stack dataSourceTransactionManagerStack,
            Stack transactionStatuStack) {
        while (!dataSourceTransactionManagerStack.isEmpty()) {
            dataSourceTransactionManagerStack.pop().rollback(
                    transactionStatuStack.pop());
        }
    }
}

service 1 - 2 :

/**
 * 一些实验性Demo接口
 *
 * @author Liukx
 * @create 2017-07-28 10:11
 * @email [email protected]
 **/
public interface IDemoService {

    public void mvpManage() throws Exception;

}

/**
 * 一些实验性Demo接口
 *
 * @author Liukx
 * @create 2017-07-28 10:11
 * @email [email protected]
 **/
public interface IDemoService2 {

    public void beidouMange() throws CoreException;

}

service实现类:


/**
 * 实现性Demo接口实现
 *
 * @author Liukx
 * @create 2017-07-28 10:12
 * @email [email protected]
 **/
@Service("demoService")
public class DemoServiceImpl implements IDemoService {

    @Autowired
    @Qualifier("demoTestDao")
    private IDemoTestDao testDao;

    @Autowired
    private IDemoService2 demoService2;

//    @Autowired
//    private DataSourceTransactionManager transactionManager;

    //TODO 这个注解很关键 - 必须是这个注解才能被拦截器拦截到
    @MultiTransactional(values = {DataSource.mvp, DataSource.system})
    public void mvpManage() throws CoreException {
        System.out.println("=====================开始处理MVP");
        testDao.insert();
        List> select = testDao.select();
        System.out.println("===============>>>>>>>>>>>>>>>>>" + select.size());
        System.out.println("=====================结束处理MVP");

        demoService2.beidouMange();

//        System.out.println("业务处理完成,开始报错");
        int i = 1 / 0;
    }

}

service 2实现

/**
 * @author Liukx
 * @create 2017-07-28 11:07
 * @email [email protected]
 **/
@Service("demo2")
public class DemoServiceImpl2 implements IDemoService2 {
    @Autowired
    @Qualifier("beidouTestDao")
    private IDemoTestDao testDao;

    @MultiTransactional(values = DataSource.system)
    public void beidouMange() throws CoreException {
        System.out.println("=====================开始处理北斗");
        testDao.insert();
        List> select = testDao.select();
        System.out.println("========================================>" + select.size());
        System.out.println("=====================结束处理北斗");
    }
}

dao实现: 这里就只列入实现类

@Repository("beidouTestDao")
public class BeiDouTestDaoImpl extends BaseDao implements IDemoTestDao {
    
    
    /**
     * 我们这块目前是有一些封装的,不过你不用太关注;
     * 你只要把对应的数据源指定好就行了
     */
    @Autowired
    @Qualifier("jdbcTemplate2")
    public JdbcTemplate jdbcTemplate;

    public int insert() throws CoreException {
        LinkedHashMap params = new LinkedHashMap<>();
        params.put("name", "某某某" + RandomUtils.randomNum(3));
        params.put("created", new Date());
        return executeInsert("test.insert", params);
    }

    @Override
    public List> select() throws CoreException {
        return findList("test.findAll", new LinkedHashMap());
    }
}
@Repository("demoTestDao")
public class DemoTestDaoImpl extends BaseDao implements IDemoTestDao {
    /**
     * 我们这块目前是有一些封装的,不过你不用太关注;
     * 你只要把对应的数据源指定好就行了
     */
    @Autowired
    @Qualifier("jdbcTemplate")
    public JdbcTemplate jdbcTemplate;

    public int insert() throws CoreException {
        LinkedHashMap params = new LinkedHashMap<>();
        params.put("name", "某某某" + RandomUtils.randomNum(2));
        params.put("created", new Date());
        return executeInsert("test.insert", params);
    }

    public List> select() throws CoreException {
        return findList("test.findAll", new LinkedHashMap());
    }
}

另外有一个动态获取bean的工具类:

@Component
public class SpringUtils implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public Object getBean(String beanId) {
        return applicationContext.getBean(beanId);
    }
}

PS : 这个方案会存在一定的问题 -> 就是如果你下一个另一个数据库操作的业务serivce方法中如果定义了@Transactional它将会又开启一个事物去执行...

方案2 : 利用JTA+Atomikios实现事物管理,业务层代码和上面差不多,主要是配置文件这块.

applicationContext-datasource.xml



          
        
        
        
        
        
        
        
        
        
    

    
    
        
        
            
                ${default.driverClassName}
                ${default.url}
                ${default.password}
                 
                ${default.username}   
                0
                20 
                0
                60000
                ${default.validationQuery}
                false
                false
                true
                true
                1800
                true
                mergeStat
            
        
    

    
        
        
            
                ${system.driverClassName}
                ${system.url}
                ${system.password}
                
                ${system.username}
                0
                20
                0
                60000
                ${system.validationQuery}
                false
                false
                true
                true
                1800
                true
                mergeStat
            
        
    
    
    
    
        
    

    
        
    
    

applicationContext-atomikos.xml




    配置事物
    
    
        
            true
        
    

    
        
    
    
    
        
        
        
        
    

    
        
        
    
    
    
        
            
            
            
            
            
            
        
    
    
    

pom.xml


        
            javax.transaction
            jta
            1.1
        
        
            com.atomikos
            atomikos-util
            4.0.2
        
        
            com.atomikos
            transactions
            4.0.2
        
        
            com.atomikos
            transactions-jta
            4.0.2
        
        
            com.atomikos
            transactions-jdbc
            4.0.2
        
        
            com.atomikos
            transactions-api
            4.0.2
        
        
            cglib
            cglib-nodep
            3.2.2
        

业务层代码和方案一的差不多,看了一下大概的思路:

  1. 在调用被拦截器匹配的方法时,开启一个新的事物

  2. 当执行到DML操作时,会获取他对应的数据源,并且会和当前线程的事物管理器中的数据源进行匹配,如果不存在,则到连接池中获取一个新的连接,并把这个连接放入当前线程的事物管理器中进行管理

  3. 当所有业务执行完毕,并且没有报错的时候,会执行一个两阶段提交的方式

    PREPARE TRANSACTION transaction_id PREPARE TRANSACTION 为当前事务的两阶段提交做准备。 在命令之后,事务就不再和当前会话关联了;它的状态完全保存在磁盘上, 它提交成功有非常高的可能性,即使是在请求提交之前数据库发生了崩溃也如此。这条命令必须在一个用BEGIN显式开始的事务块里面使用。
    COMMIT PREPARED transaction_id 提交已进入准备阶段的ID为transaction_id的事务
    ROLLBACK PREPARED transaction_id 回滚已进入准备阶段的ID为transaction_id的事务

推荐使用第二种方式,因为它有比较好的连接池以及相对完善的机制,第一种考虑的情况比较少,会出现问题,当然,你如果愿意折腾自己写一套,可以参考一下..

以上为个人学习参考,有什么不足的地方欢迎指正.

参考博客:
Mybatis + JTA http://blog.csdn.net/zmx729618/article/details/54344296
分布式事物提交以及JTA的概念 : http://www.jasongj.com/big_data/two_phase_commit/
JTA一些实现原理:https://www.ibm.com/developerworks/cn/java/j-lo-jta/
两阶段事物提交:http://m635674608.iteye.com/blog/2322853

你可能感兴趣的:(Spring + Jta +JDBCTemplate 分布式事物实现方式)