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_陈寒20 分钟前
JavaScript实战技巧总结
前端·人工智能·后端
_Evan_Yao23 分钟前
return 的迷途:try-catch-finally 中 return 的诡异顺序与 Spring 事务暗坑
java·后端·spring·mybatis
未来龙皇小蓝35 分钟前
SpringBoot API日志系统设计-02:线程池异步化与RabbitMQ解耦
数据库·spring boot·后端·性能优化·rabbitmq·java-rabbitmq
小杍随笔1 小时前
【Tauri 2 + Rust 配置 WebView2 缓存数据存储到安装目录】
开发语言·后端·rust·tauri
楼田莉子1 小时前
仿Muduo的高并发服务器:基于Tcp协议的回显服务器
linux·服务器·c++·后端
咖啡八杯1 小时前
GoF设计模式——工厂方法模式
java·后端·设计模式
程序员飞哥11 小时前
重构 AI 思维(一):Prompt Engineering,如何下达不可违抗的指令?
人工智能·后端
皮皮林55112 小时前
@Autowired 和 @Resource 注解有啥区别?你这项目怎么还混着用呢?
后端
程序员小假13 小时前
HTTP3 性能更好,为什么内网微服务依然多用 HTTP2?HTTP2 内网优势是什么?
java·后端