分布式数据库事务方案(拓展spring的事务管理器)

先推荐一下码云上的一个GVP(最有价值的开源项目) AgileBPM,我下面讲解的方案也是它的Bo支持多数据源操作事务管理器,友情链接:http://doc.agilebpm.cn/

ps:之前本人试过使用jta事务管理器,这个性能真看不下去。一会就卡。。所以就想着自己定义个管理器,自己来释放资源。

1 用AbstractRoutingDataSource让系统支持多数据源

动态数据源配置:

真正的数据源(druid数据源):

展示一下DynamicDataSource是继承了AbstractRoutingDataSource的实现,这里不是重点。

2 实现支持这种路由数据源的事务管理器

先继承AbstractPlatformTransactionManager(事务管理器的抽象类,我们很常用的DataSourceTransactionManager就是继承它的)

里面需要实现几个关键点就行(笔者只考虑了事务传播性为PROPAGATION_REQUIRED的情况,这也是项目最常用的,其他我没支持,毕竟是定制化的事务管理器)

package com.dstz.bus.service.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.sql.DataSource;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.dstz.base.core.util.ThreadMapUtil;
import com.dstz.base.db.datasource.DataSourceUtil;

/**
 * <pre>
 * 描述:ab 结合sys多数据源操作 专门为bo db实例化做的事务管理器
 * 它只保护系统数据源(包含dataSourceDefault),不会保护datasource
 * 其实可以做到,但是这个事务管理器目前只为bo多数据源的保护,所以我没支持
 * 作者:aschs
 * 邮箱:[email protected]
 * 日期:2018年10月10日
 * 版权:summer
 * </pre>
 */
public class AbDataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {

	@Override
	public void afterPropertiesSet() throws Exception {
		logger.debug("ab的事务管理器已就绪");
	}

	@Override
	public Object getResourceFactory() {
		return DataSourceUtil.getDataSourceByAlias(DataSourceUtil.GLOBAL_DATASOURCE);
	}

	/**
	 * <pre>
	 * 生成一个在整个事务处理都用到的资源
	 * 这里我放了在过程中的所有连接 Map<数据源别名,连接>
	 * </pre>
	 */
	@Override
	protected Object doGetTransaction() {
		return new HashMap<String, Connection>();
	}
	
	/**
	 * 判断是否已存在事务
	 */
	@Override
	protected boolean isExistingTransaction(Object transaction) {
		return (boolean) ThreadMapUtil.getOrDefault("abTransactionManager", false);
	}
	
	/**
	 * <pre>
	 * 准备事务,获取链接
	 * </pre>
	 */
	@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
		Map<String, Connection> conMap = (Map<String, Connection>) transaction;
		Map<String, DataSource> dsMap = DataSourceUtil.getDataSources();
		// 遍历系统中的所有数据源,打开连接
		for (Entry<String, DataSource> entry : dsMap.entrySet()) {
			Connection con = null;
			try {
				ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(entry.getValue());
				if (conHolder == null) {
					con = entry.getValue().getConnection();
					con.setAutoCommit(false);
					// 缓存链接
					TransactionSynchronizationManager.bindResource(entry.getValue(), new ConnectionHolder(con));
				} else {
					con = conHolder.getConnection();
				}
				conMap.put(entry.getKey(), con);
				logger.debug("数据源别名[" + entry.getKey() + "]打开连接成功");
			} catch (Throwable ex) {
				doCleanupAfterCompletion(conMap);
				throw new CannotCreateTransactionException("数据源别名[" + entry.getKey() + "]打开连接错误", ex);
			}
		}
		ThreadMapUtil.put("abTransactionManager", true);//标记ab事务管理器已经在线程内启动了
	}

	@Override
	protected void doCommit(DefaultTransactionStatus status) {
		Map<String, Connection> conMap = (Map<String, Connection>) status.getTransaction();
		for (Entry<String, Connection> entry : conMap.entrySet()) {
			try {
				entry.getValue().commit();
				logger.debug("数据源别名[" + entry.getKey() + "]提交事务成功");
			} catch (SQLException ex) {
				doCleanupAfterCompletion(conMap);
				throw new TransactionSystemException("数据源别名[" + entry.getKey() + "]提交事务失败", ex);
			}
		}
	}
	
	/**
	 * 回滚
	 */
	@Override
	protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
		Map<String, Connection> conMap = (Map<String, Connection>) status.getTransaction();
		for (Entry<String, Connection> entry : conMap.entrySet()) {
			try {
				entry.getValue().rollback();
				logger.debug("数据源别名[" + entry.getKey() + "]回滚事务成功");
			} catch (SQLException ex) {
				doCleanupAfterCompletion(conMap);
				throw new TransactionSystemException("数据源别名[" + entry.getKey() + "]回滚事务失败", ex);
			}
		}
	}
	
	/**
	 * 回收链接
	 */
	@Override
	protected void doCleanupAfterCompletion(Object transaction) {
		Map<String, Connection> conMap = (Map<String, Connection>) transaction;
		for (Entry<String, Connection> entry : conMap.entrySet()) {
			DataSource dataSource = DataSourceUtil.getDataSourceByAlias(entry.getKey());
			TransactionSynchronizationManager.unbindResource(dataSource);
			DataSourceUtils.releaseConnection(entry.getValue(), dataSource);
			logger.debug("数据源别名[" + entry.getKey() + "]关闭链接成功");
		}
	}
}

 事务管理器的方法调用顺序和时机大概说一下:

1 doGetTransaction方法:来初始化事务处理过程中的公共资源,后面调用的其他方法都是以它为媒介的。

2 doBegin方法:开始事务操作,主要是打开数据源的链接,记得要放到事务资源管理服务中TransactionSynchronizationManager,非常重要,因为这个过程中用到的jdbc操作是从这里面拿的。

3 doCommit(doRollback):如题,把获取的链接提交或者回滚操作。

4 doCleanupAfterCompletion:回收链接资源

至此,事务管理器的逻辑已经结束了~

最后,实现务必实现isExistingTransaction,用来处理重复线程内多次触发了事务切面的逻辑

这里笔者用简单的线程变量来标记是否线程内已存在了事务管理,因为我只支持PROPAGATION_REQUIRED传播性,所以没考虑内部嵌入的其他情况,其实也是内部commit一下,资源肯定是最后统一释放的。

3 使用自定义事务管理器

先提一下,这里笔者只保护会使用到多数据源的模块,其实大部分系统逻辑还是用DataSourceTransactionManager就够,不需要保护太多数据源(因为释放和打开链接是有性能损耗的)。

可以看出,主要的逻辑系统还是使用传统管理器,然后在特定地方声明特殊管理器则可:

5 到这里,整个分布式事务管理已完成了,主要是利用了路由数据源AbstractRoutingDataSource和自定义事务管理器实现的~

6 AgileBPM的多数据源结合展示(可跳过)

这里展示一下,这个开源的流程系统的强大可配置性(让人发指的灵活性-。-)的数据源管理功能。

数据源模板,在这里你可以使用定义不同的数据源实现类,只要在项目import就行

这里有一个内置的阿里的数据源,后面有需要你可以增加其他模板,例如BasicDataSOurce这个,最常用的数据源。

有了模板,就可以新建数据源了:

这里的是特殊默认数据源,系统本地数据源,用户可以随便添加。

然后,我就基于AgileBPM的强大数据源管理下,进行了分布式数据源测试。测试逻辑很简单,就是在一个线程内,操作多个数据源,然后看一下会不会一起回滚。

 这里展示了一下,AgileBPM中使用数据源的便捷性,根据配置的别名,直接拿来代码开发则可,测试代码比较随意了,能保证一致性。

 

猜你喜欢

转载自www.cnblogs.com/aschs/p/9771302.html