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个事务了。

相关推荐
IT_陈寒10 分钟前
Python闭包里藏的这个坑,差点让我加班到凌晨
前端·人工智能·后端
IT_陈寒10 分钟前
Java注解空指针?这个坑我踩得莫名其妙
前端·人工智能·后端
土狗TuGou1 小时前
SQL内功笔记 · 第8篇:事务的四大特性与隔离级别
数据库·笔记·后端·sql·mysql·oracle
ZengLiangYi1 小时前
React Query + REST API 最佳实践
javascript·后端·react.js
星浩AI1 小时前
项目实战:合同智能审批 · LangGraph + HITL 人机协同方案 [有源码]
后端·langchain·agent
JavaGuide1 小时前
Codex 接入第三方模型 DeepSeek、GLM、Kimi 教程:CC-Switch 和 Codex++ 两种方案对比
后端·ai编程
ZengLiangYi1 小时前
Fastify 加 Electron:把 Web 服务嵌进桌面应用
前端·javascript·后端
李白你好2 小时前
页面资产梳理 · 技术指纹识别 · Spring 端点探测
java·后端·spring
用户1753721240332 小时前
02《面向对象设计原则:SOLID原则实战解析》
后端
我是一颗柠檬2 小时前
【Java后端技术亮点】热Key探测与本地缓存二级防护:Redis热点问题的终极解决方案
java·redis·后端·缓存·中间件