事务超时异常:org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Sun Jun 25 17:34:03 CST 2023

发布时间 2023-06-25 17:47:19作者: 周文豪

报错如下:

代码如下:

Controller

import com.zwh.service.impl.TimeOutService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/timeout")
@Slf4j
public class DemoController {
    @Autowired
    private TimeOutService timeOutService;

    @GetMapping("/jdbc")
    public void queryPage() {
        timeOutService.testAnnotationTransactionalTimeOutWithJdbcTemplate();
    }
}

service

import com.zwh.dao.StudentDaoWithJdbcTemplate;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

@Service
public class TimeOutService {
    @Resource
    private StudentDaoWithJdbcTemplate studentDaoWithJdbcTemplate;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Transactional(value = "transactionManager", timeout = 2)
    public void testAnnotationTransactionalTimeOutWithJdbcTemplate() {
        try {
            Thread.sleep(3000);
        } catch (Exception e) {

        }
        studentDaoWithJdbcTemplate.update();
    }
}

jdbcTemplate

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.SQLException;
import java.sql.Statement;
@Component
public class StudentDaoWithJdbcTemplate {
    @Resource
    private JdbcTemplate jdbcTemplate;

    public void update() {

        jdbcTemplate.execute(new StatementCallback<Integer>() {
            public Integer doInStatement(Statement stmt) throws SQLException, DataAccessException {
                stmt.execute("update test set name='zhangsan3' where id = 2;");
                return 1;
            }

        });

    }
}

数据库表test如下:

使用postman访问:http://localhost:8090/api/timeout/jdbc

原因分析:

我们平时在使用spring框架开发项目时,喜欢用@Transactional注解声明事务。如上面的:

@Transactional(value = "transactionManager", timeout = 2)

只需在需要使用事务的方法上,使用@Transactional注解声明一下,该方法通过AOP就自动拥有了事务的功能。

没错,这种做法给我们带来了极大的便利,开发效率更高了。

但也给我们带来了很多隐患,比如大事务的问题。我们一起看看下面的这段代码:

复制代码
@Transactional(rollbackFor = Throwable.class)
public void updateUser(User user) {
    User oldUser = userMapper.getUserById(user.getId());
    if(null != oldUser) {
       userMapper.update(user);
    } else {
       userMapper.insert(user);
    }
    sendMq(user);
}
复制代码

这段代码中getUserById方法和sendMq方法,在这个案例中无需使用事务,只有update或insert方法才需要事务。

所以上面这段代码的事务太大了,是整个方法级别的事务。假如sendMq方法是一个非常耗时的操作,则可能会导致整个updateUser方法的事务超时,从而出现大事务问题。

timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位

那么,如何解决这个问题呢?

答:可以使用TransactionTemplate的编程式事务优化代码。

import com.zwh.dao.StudentDaoWithJdbcTemplate;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;

@Service
public class TimeOutService {
    @Resource
    private StudentDaoWithJdbcTemplate studentDaoWithJdbcTemplate;
    @Autowired
    private TransactionTemplate transactionTemplate;
public void testAnnotationTransactionalTimeOutWithJdbcTemplate() { try { Thread.sleep(3000); } catch (Exception e) { } transactionTemplate.execute(new TransactionCallbackWithoutResult() { @SneakyThrows protected void doInTransactionWithoutResult(TransactionStatus status) { try { studentDaoWithJdbcTemplate.update(); } catch (Exception e) { status.setRollbackOnly(); throw e; } } }); } }

注意:这里没有了@Transactional(value = "transactionManager", timeout = 2)注解