在spring中如何使用事务是一个很大的问题,其中有一个我需要在这篇文章中着重讲解一下,就是spring事务中的超时时间问题,很多同学不知道如何才能是这个超时时间生效,导致在使用过程中出现各种各样的问题。
1、使用mybatis框架来做数据库操作
代码案例如下:
@Transactional(propagation = Propagation.REQUIRED,timeout = 5)
@Override
public int addArea(ConsultConfigArea area) {
int i = commonMapper.addArea(area);
try {
TimeUnit.SECONDS.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
return i;
}
可以看到在@Transactional注解里面设置了一个timeout=5,这里的时间单位是秒。我们在程序里面休眠了7秒,我们测试一下:
保存一个“XJ77”的数据,测试代码如下:
@Test
public void addAreaTest() {
AreaService bean = applicationContext.getBean(AreaService.class);
ConsultConfigArea area = new ConsultConfigArea();
area.setAreaCode("XJ77");
area.setAreaName("XJ77");
area.setState("1");
bean.addArea(area);
}
单元测试的日志打印如下:
从日志打印效果来看,确实是已经提交了事务的,跟我们的预期不一样,事务设置的超时时间是5s,休眠了7s但是事务还是提交了。
数据库中的数据:
数据也成功的插入了
从这个现象来看,是不是意味着spring事务注解设置的timeout失效了呢?
为什么会这样呢?我们来读读源码
1、首先我们看看@Transactional(propagation = Propagation.REQUIRED,timeout = 5)中的timeout是如何读取的。
我们来到事务通知类TransactionInterceptor
开启事务
开启事务
dobegin
在dobegin中从连接池中获取到连接对象
在dobegin方法中把超时时间设置到一个deadline变量中
以上就是spring中把超时时间设置到了一个deadline变量中,接下来我们读读mybatis源码:
int i = commonMapper.addArea(area);执行流程
MapperProxy
最终执行到这里
进入到prepareStatement方法
从这里获取超时时间,我们看看transaction.getTimeout().
从ThreadLocal中获取连接对象,这个ThreadLocal的值是spring中的dobegin方法中设置的
把从spring事务中配置的超时时间设置到了statement对象中
从mybatis源码来看,spring事务注解设置的超时时间,最终在mybatis里面是设置到了statement对象中了,是用jdbc对象来控制sql的执行时间的,如果执行时间超过了设置时间就会抛出异常,这个异常就会被spring事务切面捕获到最终导致事务回滚
其实这里休眠10s是不影响mybatis的sql执行的,sql执行就是5s,如果超过5s就会抛出异常,而这个10s是我们的业务代码不会影响mybatis的sql执行。
所以透过现象看本质,我们业务代码里面休眠10s最终事务提交了,这个是正常现象,因为sql执行没有超过5s的。
源码学习地址:
https://study.163.com/course/courseMain.htm?courseId=1211511808&share=2&shareId=480000001878470