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

  1. 进入MybatisMapperProxy类invoke方法
  2. 进入method.invoke(this, args)方法
  3. 进入this.mapperMethod.execute(sqlSession, args)方法转到MybatisMapperMethod类execute方法
  4. 进入sqlSession.insert(this.command.getName(), param)方法转到SqlSessionTemplate类insert方法
  5. 进入this.sqlSessionProxy.insert(statement, parameter)方法转到SqlSessionInterceptor类invoke方法
  6. 进入425行方法转到DefaultSqlSession类update(statement, parameter)方法
  7. 进入最后返回的executor.update(ms, wrapCollection(parameter))方法转到CachingExecutor类
  8. 进入最后返回的delegate.update(ms, parameterObject)方法转到BaseExecutor类
  9. 进入最后返回的doUpdate(ms, parameter)方法转到SimpleExecutor类prepareStatement(handler, ms.getStatementLog())方法

    这里可以看到获得transaction设置的timeout了
  10. 进入90行的transaction.getTimeout()方法转到SpringManagedTransaction类
  11. 进入最后返回的holder.getTimeToLiveInSeconds()方法转到ResourceHolderSupport类

    这里已经可以看到有时间相关的内容了,往上滑动就能看到setTimeoutInSeconds方法setTimeoutInMillis,在这里打一个断点,如果重新请求,请求后就先到这个断点设置时间,然后才进入userMapper.insert(userInfo)方法
  12. 进入getTimeToLiveInMillis()方法

    可以看到checkTransactionTimeout(timeToLive <= 0)方法了
  13. 进入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 2024rollback第一行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>
posted on 2024-08-06 15:36  不爱吃蘑菇的大蘑菇  阅读(121)  评论(0编辑  收藏  举报