事务超时时间的错误理解

java开发,无非数据库,spring等一些技术,在公司码代码,一直有用到事务这个东西,按说对这个也很熟悉了,今天突然发现一个"奇怪"的现象.

首先pom文件是这样的,用的spring-boot1.5.20,spring版本为<spring.version>4.3.23.RELEASE</spring.version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.20.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

写了如下的代码,设置事务超时时间为2s,线程休眠了3s.

首先下面这样的写法事务是肯定会起作用的
import org.springframework.transaction.annotation.Transactional;

    @Autowired
    private TestRepository testRepository;

    @Override
    @Transactional(propagation =Propagation.REQUIRES,rollbackFor = Exception.class, timeout = 2)
    public void test() throws Exception {
        Thread.sleep(3000);
        Test a = new Test();
        a.setMsg("TEST");
        a.setFlag(true);
        Test save = testRepository.save(a);
        System.out.println(save);
        //Thread.sleep(3000);
    }

重点来了

如果我现在使用这种写法的话,事务就不管用了~
import org.springframework.transaction.annotation.Transactional;

    @Autowired
    private TestRepository testRepository;

    @Override
    @Transactional(propagation =Propagation.REQUIRES,rollbackFor = Exception.class, timeout = 2)
    public void test() throws Exception {
        //Thread.sleep(3000);
        Test a = new Test();
        a.setMsg("TEST");
        a.setFlag(true);
        Test save = testRepository.save(a);
        System.out.println(save);
        Thread.sleep(3000);
    }

出现这种的问题,肯定不是灵异事件,这肯定是自己对事务的认识肯定有问题的,所以我查了很多资料,但是仍然没有看明白,最后终于找到了.

在此需要分析下DataSourceTransactionManager的源码,我发现这里会先调用doBegin这个方法

--------
	int timeout = determineTimeout(definition);
	if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
		txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
	}
-------

这个时候就会发现TransactionDefinition.TIMEOUT_DEFAULT,这个是我们在为事务设置超时时间的参数,我们继续往下找

	/**
	 * Set the timeout for this object in seconds.
	 * @param seconds number of seconds until expiration
	 */
	public void setTimeoutInSeconds(int seconds) {
		setTimeoutInMillis(seconds * 1000L);
	}

	/**
	 * Set the timeout for this object in milliseconds.
	 * @param millis number of milliseconds until expiration
	 */
	public void setTimeoutInMillis(long millis) {
		this.deadline = new Date(System.currentTimeMillis() + millis);
	}

这时候找了dealine这个参数,在ResourceHolderSupport这个类中,我发现了这几个方法使用了它.


	/**
	 * Return the time to live for this object in seconds.
	 * Rounds up eagerly, e.g. 9.00001 still to 10.
	 * @return number of seconds until expiration
	 * @throws TransactionTimedOutException if the deadline has already been reached
	 */
	public int getTimeToLiveInSeconds() {
		double diff = ((double) getTimeToLiveInMillis()) / 1000;
		int secs = (int) Math.ceil(diff);
		checkTransactionTimeout(secs <= 0);
		return secs;
	}

	/**
	 * Return the time to live for this object in milliseconds.
	 * @return number of millseconds until expiration
	 * @throws TransactionTimedOutException if the deadline has already been reached
	 */
	public long getTimeToLiveInMillis() throws TransactionTimedOutException{
		if (this.deadline == null) {
			throw new IllegalStateException("No timeout specified for this resource holder");
		}
		long timeToLive = this.deadline.getTime() - System.currentTimeMillis();
		checkTransactionTimeout(timeToLive <= 0);
		return timeToLive;
	}

	/**
	 * Set the transaction rollback-only if the deadline has been reached,
	 * and throw a TransactionTimedOutException.
	 */
	private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {
		if (deadlineReached) {
			setRollbackOnly();
			throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
		}
	}

实际上getTimeToLiveInSecondsgetTimeToLiveInMillis都调用了checkTransactionTimeout这个方法,这个checkTransactionTimeout方法中将rollbackOnly设置为了true,然后抛出TransactionTimedOutException异常.我们继续看getTimeToLiveInSeconds被谁调用.

DataSourceUtils中的applyTimeout方法中,

	/**
	 * Apply the specified timeout - overridden by the current transaction timeout,
	 * if any - to the given JDBC Statement object.
	 * @param stmt the JDBC Statement object
	 * @param dataSource the DataSource that the Connection was obtained from
	 * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction)
	 * @throws SQLException if thrown by JDBC methods
	 * @see java.sql.Statement#setQueryTimeout
	 */
	public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {
		Assert.notNull(stmt, "No Statement specified");
		ConnectionHolder holder = null;
		if (dataSource != null) {
			holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
		}
		if (holder != null && holder.hasTimeout()) {
			// Remaining transaction timeout overrides specified value.
			stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
		}
		else if (timeout >= 0) {
			// No current transaction timeout -> apply specified value.
			stmt.setQueryTimeout(timeout);
		}
	}

在这里,继续查看方法调用情况applyTimeoutJdbcTemplate中的applyStatementSettings方法调用,如下图所示:
applyStatementSettings方法调用情况在执行sql语句时,无论使用的是jpa还是jdbctemplate,最后都会进入jdbctemplate中的execute方法

	@Override
	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");

		Connection con = DataSourceUtils.getConnection(getDataSource());
		Statement stmt = null;
		try {
			Connection conToUse = con;
			if (this.nativeJdbcExtractor != null &&
					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
			}
			stmt = conToUse.createStatement();
			applyStatementSettings(stmt);
			Statement stmtToUse = stmt;
			if (this.nativeJdbcExtractor != null) {
				stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
			}
			T result = action.doInStatement(stmtToUse);
			handleWarnings(stmt);
			return result;
		}
		catch (SQLException ex) {
			// Release Connection early, to avoid potential connection pool deadlock
			// in the case when the exception translator hasn't been initialized yet.
			JdbcUtils.closeStatement(stmt);
			stmt = null;
			DataSourceUtils.releaseConnection(con, getDataSource());
			con = null;
			throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
		}
		finally {
			JdbcUtils.closeStatement(stmt);
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}

实际上到这里就已经很明朗了,实际上在执行sql语句前,先执行了判断是否含有事务超时时间,所以当方法体重如果有超时的情况,就会直接关闭这个jdbc的连接.不会去连接数据库.

但是已经执行完数据库操作后,再进行其他操作,并且没有其他数据库操作的话,这个超时时间是不会起作用的,因为根本就不会执行数据库的操作,所以这个当然不会回滚了.

从中可以发现,还是对于spring的理解不够深刻,或者说只是自己的愚昧,-_-||,还是需要多看源码哟~还是应该多学习!

以上都是在网上博客上找到的原因,自己又进行了查验,从中也学到了不少,感谢原博主!

另外:解决方法其实也有很多种:这里总结几个处理的方法

  • 使用手动回滚的方法:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  • 直接显式抛出异常throw new RuntimeException();
参考博客:

1.Spring事务超时时间可能存在的错误认识2- https://blog.csdn.net/educast/article/details/78823970
2.Spring事务超时时间可能存在的错误认识2- https://www.iteye.com/blog/m635674608-2302007

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值