结论:事务方法中调用的其他支持事务的方法
执行超时才能够触发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>