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

相关推荐
ABin-阿斌15 分钟前
SpringBoot 整合 Easy_Trans 实现翻译的具体介绍
java·spring boot·后端
终末圆29 分钟前
MyBatis XML映射文件编写【后端 18】
xml·java·开发语言·后端·算法·spring·mybatis
_.Switch35 分钟前
Python Web 架构设计与性能优化
开发语言·前端·数据库·后端·python·架构·log4j
2401_8576009539 分钟前
心理教育辅导系统的设计与Spring Boot实现
java·spring boot·后端
北飞的山羊1 小时前
【计算机网络】详解UDP套接字&网络字节序&IP地址&端口号
linux·服务器·网络·后端·计算机网络·udp·信息与通信
☼YJLH☾1 小时前
第十章,XML
xml·java·后端·intellij-idea
拜见老天師1 小时前
SpringBoot中对数据库连接配置信息进行加密处理
数据库·spring boot·后端
記億揺晃着的那天2 小时前
SpringCloud从零开始简单搭建 - JDK17
java·spring boot·后端·spring cloud·nacos
憨憨憨憨憨到不行的程序员2 小时前
Spring框架基础知识
java·后端·spring
Adolf_19932 小时前
Flask-SQLAlchemy一对多 一对一 多对多关联
后端·python·flask