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
方法调用,如下图所示:
在执行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