結論:事務方法中呼叫的其他支援事務的方法
執行超時才能夠觸發timeout
其他支援事務的方法
恐怕目前接觸的就只有資料庫操作了。
一個事務方法A(如插入)在截至時間之前執行結束後,無論後續的非事務方法執行多久都不會觸發timeout
,也就是這個事務方法A(如插入)會commit
(如插入持久化)。
下面是一個很簡單的儲存業務
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional(timeout = 10) // 10單位為秒
public void insert(UserInfo userInfo) throws InterruptedException {
int insert = userMapper.insert(userInfo);
Thread.sleep(13000); // 各種耗時操作模擬,如IO。 13000單位為毫秒
}
請求到來後,第一行方法userMapper.insert()
執行之前,ResourceHolderSupport類
會執行setTimeoutInMillis
方法,結合timeout計算出一個事務deadline"死期"
,至於這個ResourceHolderSupport類
怎麼來,順著下面Debug可以看到
流水賬預警
Debug
進入userMapper.insert()
方法
- 進入
MybatisMapperProxy類invoke方法
- 進入
method.invoke(this, args)方法
- 進入
this.mapperMethod.execute(sqlSession, args)方法
轉到MybatisMapperMethod類execute方法
- 進入
sqlSession.insert(this.command.getName(), param)方法
轉到SqlSessionTemplate類insert方法
- 進入
this.sqlSessionProxy.insert(statement, parameter)方法
轉到SqlSessionInterceptor類invoke方法
- 進入
425行方法
轉到DefaultSqlSession類update(statement, parameter)方法
- 進入最後返回的
executor.update(ms, wrapCollection(parameter))方法
轉到CachingExecutor類
- 進入最後返回的
delegate.update(ms, parameterObject)方法
轉到BaseExecutor類
- 進入最後返回的
doUpdate(ms, parameter)方法
轉到SimpleExecutor類prepareStatement(handler, ms.getStatementLog())方法
這裡可以看到獲得transaction設定的timeout了 - 進入
90行的transaction.getTimeout()方法
轉到SpringManagedTransaction類
- 進入最後返回的
holder.getTimeToLiveInSeconds()方法
轉到ResourceHolderSupport類
這裡已經可以看到有時間相關的內容了,往上滑動就能看到setTimeoutInSeconds方法
和setTimeoutInMillis
,在這裡打一個斷點,如果重新請求,請求後就先到這個斷點設定時間,然後才進入userMapper.insert(userInfo)方法
- 進入
getTimeToLiveInMillis()方法
可以看到checkTransactionTimeout(timeToLive <= 0)方法了
- 進入
checkTransactionTimeout
可以看到最終timeout異常丟擲程式碼
也就是說,沒有支援事務的方法超時然後丟擲TransactionTimedOutException異常,就不會觸發設定的timeout
進行回滾
上面定義的service
中,insert顯然是10秒中內就可以執行完的,重新執行了傳送了一次請求
這個方法還有7.5秒能活呢
直接從checkTransactionTimeout()方法
中出來,一路返回到SqlSessionTemplate類
中
下面的if
判斷了是否有SqlSession
事務,沒有就直接commit
最後執行Thread.Sleep方法
睡13秒
雖然方法總體執行時間>10秒
,但是並沒有觸發timeout
那定義一個這樣的方法行嗎?
@Override
@Transactional
public void hold() throws InterruptedException {
Thread.sleep(13000) // 各種耗時操作模擬,如IO
}
試過了,不行,不是加了一個@Transactional註解方法就支援事務了。
但是如果先執行Thread.sleep(13000)
就可以觸發timeout
,至於為什麼,前面的流水賬已經展示了timeout
觸發的過程
怎麼設計一個資料庫操作+非事務操作如何支援事務?
看到網上一個帖子中一個博主用一個額外的輕量資料庫操作放在最後來實現
如果service裡的userMapper.insert操作有冪等性,最簡單可以這樣做
@Override
@Transactional(timeout = 10)
public void insert(UserInfo userInfo) throws InterruptedException {
int insert = userMapper.insert(userInfo);
Thread.sleep(13000); // 各種耗時操作模擬,如IO
int insert1 = userMapper.insert(userInfo); // 輕量化資料庫操作方法,用來觸發timeout來rollback
}
這時伺服器就會報錯類似org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Tue Aug 06 15:02:39 CST 2024
並rollback
第一行insert
但是要注意非事務方法,如果需要rollback需要自己進行實現。
具體怎麼合理的去實現就是另外一個話題了。
其他程式碼
程式碼都是隨手寫的,沒有遵守規範
@Data
public class UserInfo {
@TableId(value = "u_id")
private int uId;
private String uName;
private String uGender;
}
@Controller
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/add")
public void addUser(@RequestBody UserInfo userInfo) throws InterruptedException {
userService.insert(userInfo);
}
}
public interface UserService {
void insert(UserInfo userInfo) throws InterruptedException;
}
@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
}
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`u_id` int NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,
`u_name` varchar(20) NOT NULL COMMENT 'name',
`u_gender` enum('male','female') NOT NULL COMMENT 'gender'
);
// 請求json
{
"uid": null,
"uname":"打打怪",
"ugender": "male"
}
// pom.xml版本
// spring版本
<spring-boot.version>2.7.6</spring-boot.version>
// myabtis-plus版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.5.7</version>
</dependency>
// lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>