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