TransactionTemplate获取事务源码浅析

最近线上环境的某个服务,总是会时不时提示连接数不够类似的提示,甚至还会有死锁的情况发生,后来经过排查,发现是某段核心代码使用编程式事务的时候,某个分支下忘记对事务进行提交,导致事务不断的进行积累,导致连接数被长时间占用,甚至出现了死锁问题。

我自己在本地想复现的时候,发现了一个有趣的现象,所以粗略的看了一下获取事务的流程源码。首先本地的测试代码就模拟一个简单的未提交事务的情况,同时设置mysql的最大连接数为20。

java 复制代码
public Member findMember(String username) {
        TransactionStatus transaction = transactionTemplate.getTransactionManager().getTransaction(null);
        Member member = memberService.findMember(username, null);
        return member;
    }

循环50次调用接口

本来按我的理解,认为应该是会发生连接数不够的情况,但实际上全部运行成功了,而且数据库里的事务数只有10个,这下,把我搞蒙了,为啥事务数低于10会产生新的事务,高于10之后就不会产生了新的事务。 点开getTransaction的源码,真相必定就在其中了。

java 复制代码
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
        //获取事务类型,null则是默认的事务类型
        TransactionDefinition def = definition != null ? definition : TransactionDefinition.withDefaults();
        //获取事务
        Object transaction = this.doGetTransaction();
        boolean debugEnabled = this.logger.isDebugEnabled();
        //判断事务是否存在
        if (this.isExistingTransaction(transaction)) {
            // 返回已存在的事务
            return this.handleExistingTransaction(def, transaction, debugEnabled);
        } else if (def.getTimeout() < -1) {
            throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
        } else if (def.getPropagationBehavior() == 2) {
            throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
            //事务类型判断
        } else if (def.getPropagationBehavior() != 0 && def.getPropagationBehavior() != 3 && def.getPropagationBehavior() != 6) {
            if (def.getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
                this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + def);
            }

            boolean newSynchronization = this.getTransactionSynchronization() == 0;
            return this.prepareTransactionStatus(def, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
        } else {  //事务不存在,则开启新事物
            SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
            if (debugEnabled) {
                this.logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
            }
            try {
                return this.startTransaction(def, transaction, debugEnabled, suspendedResources);
            } catch (Error | RuntimeException var7) {
                this.resume((Object)null, suspendedResources);
                throw var7;
            }
        }
    }

结构十分的清晰,点开isExistingTransaction(),查看判断事务是否存在的条件。

java 复制代码
    protected boolean isExistingTransaction(Object transaction) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
        //当前对象是否有连接对象,连接对象中的事务是否是活跃状态
        return txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive();
    }

回到上一步,再点开doGetTransaction()

java 复制代码
    protected Object doGetTransaction() {
        DataSourceTransactionObject txObject = new DataSourceTransactionObject();
        //是否运行事务嵌套
        txObject.setSavepointAllowed(this.isNestedTransactionAllowed());
        //获取连接对象
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.obtainDataSource());
        //设置连接对象
        txObject.setConnectionHolder(conHolder, false);
        return txObject;
    }

点开getResource(),又调用了doGetResource

java 复制代码
	public static Object getResource(Object key) {
        //构建key
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		return doGetResource(actualKey);
	}
java 复制代码
	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");
                        
	@Nullable //nullable这个习惯,真心建议大家都使用一下,避免空指针的发生
	private static Object doGetResource(Object actualKey) {
		Map<Object, Object> map = resources.get();
		if (map == null) {
			return null;
		}
                //获取ConnectionHolder
		Object value = map.get(actualKey);
		// Transparently remove ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			map.remove(actualKey);
			// Remove entire ThreadLocal if empty...
			if (map.isEmpty()) {
				resources.remove();
			}
			value = null;
		}
		return value;
	}

到这里就差不多知道原因了,事务是否存在,是根据ConnectionHolder是否存在判断的,而ConnectionHolder对象是保存的ThreadLocal中,而ThreadLocal大家也都知道,是每个线程都会维护一份,所以你有几个线程就只有几个ThreadLocal,而springboot默认使用的tomcat的minSpareThreads空闲线程数也正好就是10,最终导致了这个情况的发生。重新设置一下minSpareThreads线程数,果然50个事务了。

相关推荐
狗凯之家源码网4 分钟前
电商代付系统从零搭建与实战指南
前端·后端·开源
IT_陈寒17 分钟前
Vue组件通信这个坑我跳了两次才知道怎么爬出来
前端·人工智能·后端
copyer_xyf26 分钟前
Python 文件基本操作
前端·后端·python
西凉的悲伤38 分钟前
Spring Security + JWT 登录认证完整实践指南
java·后端·spring·spring security·jwt
砍材农夫1 小时前
物联网实战:Spring Boot MQTT | 客户端框架比对
spring boot·后端·物联网
Gopher_HBo1 小时前
存储层LSM Tree
后端·架构
copyer_xyf1 小时前
Python 迭代器与生成器
前端·后端·python
copyer_xyf10 小时前
Python 异常处理
前端·后端·python
llz_11211 小时前
web-第三次课后作业
前端·后端·web
MageGojo13 小时前
天气 API 接入实战:基于 ApiZero 实现实时天气、分钟级降水和 15 天预报查询
java·后端·spring·api 接口接入·接口实战