事务超时时间的错误理解

  • Post author:
  • Post category:其他


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);
		}
	}

实际上

getTimeToLiveInSeconds



getTimeToLiveInMillis

都调用了

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);
		}
	}

在这里,继续查看方法调用情况

applyTimeout



JdbcTemplate

中的

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



版权声明:本文为x7418520原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。