Spring 事务

一、Spring事务管理的几种方式

Spring事务在具体使用方式上可分为两大类:

1. 声明式

  • 基于 TransactionProxyFactoryBean的声明式事务管理
  • 基于 <tx><aop> 命名空间的事务管理
  • 基于 @Transactional 的声明式事务管理

    2. 编程式

  • 基于事务管理器API 的编程式事务管理

  • 基于TransactionTemplate 的编程式事务管理

目前大部分项目使用的是声明式的后两种:

  • 基于 <tx><aop> 命名空间的声明式事务管理可以充分利用切点表达式的强大支持,使得管理事务更加灵活。
  • 基于 @Transactional 的方式需要实施事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,在Spring Boot中通常也建议使用这种注解方式来标记事务。

    二、Spring事务实现机制

    详细看下Spring事务的源代码,进而了解其工作原理。从<tx>标签的解析类开始:
    1. @Override
    2. public void init() {
    3. registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
    4. registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    5. registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
    6. }
    7. }
    1. class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    2. @Override
    3. protected Class<?> getBeanClass(Element element) {
    4. return TransactionInterceptor.class;
    5. }
    6. }
    由此可看到Spring事务的核心实现类TransactionInterceptor及其父类TransactionAspectSupport,其实现了事务的开启、数据库操作、事务提交、回滚等。平时在开发时如果想确定是否在事务中,也可以在该方法进行断点调试。
    **TransactionInterceptor**
    1. public Object invoke(final MethodInvocation invocation) throws Throwable {
    2. Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    3. // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    4. return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
    5. @Override
    6. public Object proceedWithInvocation() throws Throwable {
    7. return invocation.proceed();
    8. }
    9. });
    10. }
    **TransactionAspectSupport**
    1. protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
    2. throws Throwable {
    3. // If the transaction attribute is null, the method is non-transactional.
    4. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    5. final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    6. final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    7. if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    8. // Standard transaction demarcation with getTransaction and commit/rollback calls.
    9. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    10. Object retVal = null;
    11. try {
    12. // This is an around advice: Invoke the next interceptor in the chain.
    13. // This will normally result in a target object being invoked.
    14. retVal = invocation.proceedWithInvocation();
    15. }
    16. catch (Throwable ex) {
    17. // target invocation exception
    18. completeTransactionAfterThrowing(txInfo, ex);
    19. throw ex;
    20. }
    21. finally {
    22. cleanupTransactionInfo(txInfo);
    23. }
    24. commitTransactionAfterReturning(txInfo);
    25. return retVal;
    26. }
    27. }
    至此了解事务的整个调用流程,但还有一个重要的机制没分析到,那就是Spring 事务针对不同的传播级别控制当前获取的数据库连接。接下来看下Spring获取连接的工具类DataSourceUtils,JdbcTemplate、Mybatis-Spring也都是通过该类获取Connection。
    1. public abstract class DataSourceUtils {
    2. public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
    3. try {
    4. return doGetConnection(dataSource);
    5. }
    6. catch (SQLException ex) {
    7. throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
    8. }
    9. }
    10. public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    11. Assert.notNull(dataSource, "No DataSource specified");
    12. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    13. if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
    14. conHolder.requested();
    15. if (!conHolder.hasConnection()) {
    16. logger.debug("Fetching resumed JDBC Connection from DataSource");
    17. conHolder.setConnection(dataSource.getConnection());
    18. }
    19. return conHolder.getConnection();
    20. }
    21. }
    TransactionSynchronizationManager也是一个事务同步管理的核心类,它实现了事务同步管理的职能,包括记录当前连接持有connection holder。
    **TransactionSynchronizationManager**
    1. private static final ThreadLocal<Map<Object, Object>> resources =
    2. new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
    3. public static Object getResource(Object key) {
    4. Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    5. Object value = doGetResource(actualKey);
    6. if (value != null && logger.isTraceEnabled()) {
    7. logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
    8. Thread.currentThread().getName() + "]");
    9. }
    10. return value;
    11. }
    12. /**
    13. * Actually check the value of the resource that is bound for the given key.
    14. */
    15. private static Object doGetResource(Object actualKey) {
    16. Map<Object, Object> map = resources.get();
    17. if (map == null) {
    18. return null;
    19. }
    20. Object value = map.get(actualKey);
    21. // Transparently remove ResourceHolder that was marked as void...
    22. if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
    23. map.remove(actualKey);
    24. // Remove entire ThreadLocal if empty...
    25. if (map.isEmpty()) {
    26. resources.remove();
    27. }
    28. value = null;
    29. }
    30. return value;
    31. }
    在事务管理器类AbstractPlatformTransactionManager中,getTransaction获取事务时,会处理不同的事务传播行为,例如当前存在事务,但调用方法事务传播级别为REQUIRES_NEWPROPAGATION_NOT_SUPPORTED时,对当前事务进行挂起、恢复等操作,以此保证了当前数据库操作获取正确的Connection
    具体是在子事务提交的最后会将挂起的事务恢复,恢复时重新调用TransactionSynchronizationManager. bindResource设置之前的connection holder,这样再获取的连接就是被恢复的数据库连接, TransactionSynchronizationManager当前激活的连接只能是一个。
    **AbstractPlatformTransactionManager**
    1. private TransactionStatus handleExistingTransaction(
    2. TransactionDefinition definition, Object transaction, boolean debugEnabled)
    3. throws TransactionException {
    4. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
    5. if (debugEnabled) {
    6. logger.debug("Suspending current transaction, creating new transaction with name [" +
    7. definition.getName() + "]");
    8. }
    9. SuspendedResourcesHolder suspendedResources = suspend(transaction);
    10. try {
    11. boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    12. DefaultTransactionStatus status = newTransactionStatus(
    13. definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    14. doBegin(transaction, definition);
    15. prepareSynchronization(status, definition);
    16. return status;
    17. }
    18. catch (RuntimeException beginEx) {
    19. resumeAfterBeginException(transaction, suspendedResources, beginEx);
    20. throw beginEx;
    21. }
    22. catch (Error beginErr) {
    23. resumeAfterBeginException(transaction, suspendedResources, beginErr);
    24. throw beginErr;
    25. }
    26. }
    27. /**
    28. * Clean up after completion, clearing synchronization if necessary,
    29. * and invoking doCleanupAfterCompletion.
    30. * @param status object representing the transaction
    31. * @see #doCleanupAfterCompletion
    32. */
    33. private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    34. status.setCompleted();
    35. if (status.isNewSynchronization()) {
    36. TransactionSynchronizationManager.clear();
    37. }
    38. if (status.isNewTransaction()) {
    39. doCleanupAfterCompletion(status.getTransaction());
    40. }
    41. if (status.getSuspendedResources() != null) {
    42. if (status.isDebug()) {
    43. logger.debug("Resuming suspended transaction after completion of inner transaction");
    44. }
    45. resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
    46. }
    47. }
    Spring的事务是通过AOP代理类中的一个Advice(TransactionInterceptor)进行生效的,而传播级别定义了事务与子事务获取连接、事务提交、回滚的具体方式。
    AOP(Aspect Oriented Programming),即面向切面编程。Spring AOP技术实现上其实就是代理类,具体可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;(AspectJ);而动态代理则在运行时借助于 默写类库在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。其中java是使用的动态代理模式 (JDK+CGLIB)。
    JDK动态代理 JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
    CGLIB动态代理 CGLIB全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLIB封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLIB创建动态代理。
    CGLIB 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。如果在运行的时候不断地用 CGLIB 去创建代理,系统的性能会大打折扣。因此如果有接口,Spring默认使用JDK 动态代理,源代码如下:
    1. public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    2. @Override
    3. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    4. if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    5. Class<?> targetClass = config.getTargetClass();
    6. if (targetClass == null) {
    7. throw new AopConfigException("TargetSource cannot determine target class: " +
    8. "Either an interface or a target is required for proxy creation.");
    9. }
    10. if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    11. return new JdkDynamicAopProxy(config);
    12. }
    13. return new ObjenesisCGLIBAopProxy(config);
    14. }
    15. else {
    16. return new JdkDynamicAopProxy(config);
    17. }
    18. }
    19. }
    在了解Spring代理的两种特点后,也就知道在做事务切面配置时的一些注意事项,例如JDK代理时方法必须是public,CGLIB代理时必须是public、protected,且类不能是final的;在依赖注入时,如果属性类型定义为实现类,JDK代理时会报如下注入异常:
    1. org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.wwb.test.TxTestAop': Unsatisfied dependency expressed through field 'service'; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'stockService' is expected to be of type 'com.wwb.service.StockProcessServiceImpl' but was actually of type 'com.sun.proxy.$Proxy14'
    但如果修改为CGLIB代理时则会成功注入,所以如果有接口,建议注入时该类属性都定义为接口。另外事务切点都配置在实现类和接口都可以生效,但建议加在实现类上。
    官网关于Spring AOP的详细介绍

    https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html%23aop

三、Spring事务的那些坑

3.1 事务不生效

测试代码,事务AOP配置:

  1. <tx:advice id="txAdvice" transaction-manager="myTxManager">
  2. <tx:attributes>
  3. <!-- 指定在连接点方法上应用的事务属性 -->
  4. <tx:method name="openAccount" isolation="DEFAULT" propagation="REQUIRED"/>
  5. <tx:method name="openStock" isolation="DEFAULT" propagation="REQUIRED"/>
  6. <tx:method name="openStockInAnotherDb" isolation="DEFAULT" propagation="REQUIRES_NEW"/>
  7. <tx:method name="openTx" isolation="DEFAULT" propagation="REQUIRED"/>
  8. <tx:method name="openWithoutTx" isolation="DEFAULT" propagation="NEVER"/>
  9. <tx:method name="openWithMultiTx" isolation="DEFAULT" propagation="REQUIRED"/>
  10. </tx:attributes>
  11. </tx:advice>
  1. public class StockProcessServiceImpl implements IStockProcessService{
  2. @Autowired
  3. private IAccountDao accountDao;
  4. @Autowired
  5. private IStockDao stockDao;
  6. @Override
  7. public void openAccount(String aname, double money) {
  8. accountDao.insertAccount(aname, money);
  9. }
  10. @Override
  11. public void openStock(String sname, int amount) {
  12. stockDao.insertStock(sname, amount);
  13. }
  14. @Override
  15. public void openStockInAnotherDb(String sname, int amount) {
  16. stockDao.insertStock(sname, amount);
  17. }
  18. }
  19. public void insertAccount(String aname, double money) {
  20. String sql = "insert into account(aname, balance) values(?,?)";
  21. this.getJdbcTemplate().update(sql, aname, money);
  22. DbUtils.printDBConnectionInfo("insertAccount",getDataSource());
  23. }
  24. public void insertStock(String sname, int amount) {
  25. String sql = "insert into stock(sname, count) values (?,?)";
  26. this.getJdbcTemplate().update(sql , sname, amount);
  27. DbUtils.printDBConnectionInfo("insertStock",getDataSource());
  28. }
  29. public static void printDBConnectionInfo(String methodName,DataSource ds) {
  30. Connection connection = DataSourceUtils.getConnection(ds);
  31. System.out.println(methodName+" connection hashcode="+connection.hashCode());
  32. }
  1. //调用同类方法,外围配置事务
  2. public void openTx(String aname, double money) {
  3. openAccount(aname,money);
  4. openStock(aname,11);
  5. }

1.运行输出:

insertAccount connection hashcode=319558327 insertStock connection hashcode=319558327

  1. //调用同类方法,外围未配置事务
  2. public void openWithoutTx(String aname, double money) {
  3. openAccount(aname,money);
  4. openStock(aname,11);
  5. }

2.运行输出:

insertAccount connection hashcode=1333810223 insertStock connection hashcode=1623009085

  1. //通过AopContext.currentProxy()方法获取代理
  2. @Override
  3. public void openWithMultiTx(String aname, double money) {
  4. openAccount(aname,money);
  5. openStockInAnotherDb(aname, 11);//传播级别为REQUIRES_NEW
  6. }

3.运行输出:

insertAccount connection hashcode=303240439 insertStock connection hashcode=303240439

可以看到2、3测试方法跟事务预期的一样,结论:调用方法未配置事务、本类方法直接调用,事务都不生效!
究其原因,还是因为Spring的事务本质上是个代理类,而本类方法直接调用时其对象本身并不是织入事务的代理,所以事务切面并未生效。
Spring也提供了判断是否为代理的方法:

  1. public static void printProxyInfo(Object bean) {
  2. System.out.println("isAopProxy"+AopUtils.isAopProxy(bean));
  3. System.out.println("isCGLIBProxy="+AopUtils.isCGLIBProxy(bean));
  4. System.out.println("isJdkProxy="+AopUtils.isJdkDynamicProxy(bean));
  5. }

那如何修改为代理类调用呢?最直接的想法是注入自身,代码如下:

  1. @Autowired
  2. private IStockProcessService stockProcessService;
  3. //注入自身类,循环依赖,亲测可以
  4. public void openTx(String aname, double money) {
  5. stockProcessService.openAccount(aname,money);
  6. stockProcessService.openStockInAnotherDb (aname,11);
  7. }

当然Spring提供了获取当前代理的方法:代码如下:

  1. //通过AopContext.currentProxy()方法获取代理
  2. @Override
  3. public void openWithMultiTx(String aname, double money) {
  4. ((IStockProcessService)AopContext.currentProxy()).openAccount(aname,money);
  5. ((IStockProcessService)AopContext.currentProxy()).openStockInAnotherDb(aname, 11);
  6. }

另外Spring是通过TransactionSynchronizationManager类中线程变量来获取事务中数据库连接,所以如果是多线程调用或者绕过Spring获取数据库连接,都会导致Spring事务配置失效。
最后Spring事务配置失效的场景:

  1. 事务切面未配置正确
  2. 本类方法调用
  3. 多线程调用
  4. 绕开Spring获取数据库连接

    3.2 事务不回滚

    测试代码:
    1. <tx:advice id="txAdvice" transaction-manager="myTxManager">
    2. <tx:attributes>
    3. <!-- 指定在连接点方法上应用的事务属性 -->
    4. <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED"/>
    5. </tx:attributes>
    6. </tx:advice>
    1. public void buyStock(String aname, double money, String sname, int amount) throws StockException {
    2. boolean isBuy = true;
    3. accountDao.updateAccount(aname, money, isBuy);
    4. // 故意抛出异常
    5. if (true) {
    6. throw new StockException("购买股票异常");
    7. }
    8. stockDao.updateStock(sname, amount, isBuy);
    9. }
    1. @Test
    2. public void testBuyStock() {
    3. try {
    4. service.openAccount("dcbs", 10000);
    5. service.buyStock("dcbs", 2000, "dap", 5);
    6. } catch (StockException e) {
    7. e.printStackTrace();
    8. }
    9. double accountBalance = service.queryAccountBalance("dcbs");
    10. System.out.println("account balance is " + accountBalance);
    11. }
    输出结果:

    insertAccount connection hashcode=656479172 updateAccount connection hashcode=517355658 account balance is 8000.0

应用抛出异常,但accountDao.updateAccount却进行了提交。究其原因,直接看Spring源代码:
TransactionAspectSupport

  1. protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
  2. if (txInfo != null && txInfo.hasTransaction()) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
  5. "] after exception: " + ex);
  6. }
  7. if (txInfo.transactionAttribute.rollbackOn(ex)) {
  8. try {
  9. txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
  10. }
  11. catch (TransactionSystemException ex2) {
  12. logger.error("Application exception overridden by rollback exception", ex);
  13. ex2.initApplicationException(ex);
  14. throw ex2;
  15. }
  16. }
  17. public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
  18. @Override
  19. public boolean rollbackOn(Throwable ex) {
  20. return (ex instanceof RuntimeException || ex instanceof Error);
  21. }
  22. }

由代码可见,Spring事务默认只对RuntimeException和Error进行回滚,如果应用需要对指定的异常类进行回滚,可配置rollback-for=属性,例如:

  1. <!-- 注册事务通知 -->
  2. <tx:advice id="txAdvice" transaction-manager="myTxManager">
  3. <tx:attributes>
  4. <!-- 指定在连接点方法上应用的事务属性 -->
  5. <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="StockException"/>
  6. </tx:attributes>
  7. </tx:advice>

事务不回滚的原因:

  1. 事务配置切面未生效
  2. 应用方法中将异常捕获
  3. 抛出的异常不属于运行时异常(例如IOException),
  4. rollback-for属性配置不正确

    3.3 事务超时不生效

    测试代码:
    1. <!-- 注册事务通知 -->
    2. <tx:advice id="txAdvice" transaction-manager="myTxManager">
    3. <tx:attributes>
    4. <tx:method name="openAccountForLongTime" isolation="DEFAULT" propagation="REQUIRED" timeout="3"/>
    5. </tx:attributes>
    6. </tx:advice>
    1. @Override
    2. public void openAccountForLongTime(String aname, double money) {
    3. accountDao.insertAccount(aname, money);
    4. try {
    5. Thread.sleep(5000L);//在数据库操作之后超时
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. }
    1. @Test
    2. public void testTimeout() {
    3. service.openAccountForLongTime("dcbs", 10000);
    4. }
    正常运行,事务超时未生效
    1. public void openAccountForLongTime(String aname, double money) {
    2. try {
    3. Thread.sleep(5000L); //在数据库操作之前超时
    4. } catch (InterruptedException e) {
    5. e.printStackTrace();
    6. }
    7. accountDao.insertAccount(aname, money);
    8. }
    抛出事务超时异常,超时生效

    org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Nov 23 17:03:02 CST 2018 at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:141) …

通过源码看看Spring事务超时的判断机制:
ResourceHolderSupport

  1. /**
  2. * Return the time to live for this object in milliseconds.
  3. * @return number of millseconds until expiration
  4. * @throws TransactionTimedOutException if the deadline has already been reached
  5. */
  6. public long getTimeToLiveInMillis() throws TransactionTimedOutException{
  7. if (this.deadline == null) {
  8. throw new IllegalStateException("No timeout specified for this resource holder");
  9. }
  10. long timeToLive = this.deadline.getTime() - System.currentTimeMillis();
  11. checkTransactionTimeout(timeToLive <= 0);
  12. return timeToLive;
  13. }
  14. /**
  15. * Set the transaction rollback-only if the deadline has been reached,
  16. * and throw a TransactionTimedOutException.
  17. */
  18. private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {
  19. if (deadlineReached) {
  20. setRollbackOnly();
  21. throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
  22. }
  23. }

通过查看getTimeToLiveInMillis方法的Call Hierarchy,可以看到被DataSourceUtils的applyTimeout所调用, 继续看applyTimeout的Call Hierarchy,可以看到有两处调用,一个是JdbcTemplate,一个是TransactionAwareInvocationHandler类,后者是只有TransactionAwareDataSourceProxy类调用,该类为DataSource的事务代理类,一般并不会用到。难道超时只能在这调用JdbcTemplate中生效?写代码亲测:

  1. <!-- 注册事务通知 -->
  2. <tx:advice id="txAdvice" transaction-manager="myTxManager">
  3. <tx:attributes>
  4. <tx:method name="openAccountForLongTimeWithoutJdbcTemplate" isolation="DEFAULT" propagation="REQUIRED" timeout="3"/>
  5. </tx:attributes>
  6. </tx:advice>
  1. public void openAccountForLongTimeWithoutJdbcTemplate(String aname, double money) {
  2. try {
  3. Thread.sleep(5000L);
  4. } catch (InterruptedException e) {
  5. e.printStackTrace();
  6. }
  7. accountDao.queryAccountBalanceWithoutJdbcTemplate(aname);
  8. }
  9. public double queryAccountBalanceWithoutJdbcTemplate(String aname) {
  10. String sql = "select balance from account where aname = ?";
  11. PreparedStatement prepareStatement;
  12. try {
  13. prepareStatement = this.getConnection().prepareStatement(sql);
  14. prepareStatement.setString(1, aname);
  15. ResultSet executeQuery = prepareStatement.executeQuery();
  16. while(executeQuery.next()) {
  17. return executeQuery.getDouble(1);
  18. }
  19. } catch (CannotGetJdbcConnectionException | SQLException e) {
  20. // TODO Auto-generated catch block
  21. e.printStackTrace();
  22. }
  23. return 0;
  24. }

运行正常,事务超时失效

由上可见:Spring事务超时判断在通过JdbcTemplate的数据库操作时,所以如果超时后未有JdbcTemplate方法调用,则无法准确判断超时。另外也可以得知,如果通过Mybatis等操作数据库,Spring的事务超时是无效的。鉴于此,Spring的事务超时谨慎使用。

四、 总结

JDBC规范中Connection 的setAutoCommit是原生控制手动事务的方法,但传播行为、异常回滚、连接管理等很多技术问题都需要开发者自己处理,而Spring事务通过AOP方式非常优雅的屏蔽了这些技术复杂度,使得事务管理变的异常简单。
但凡事有利弊,如果对实现机制理解不透彻,很容易掉坑里。最后总结下Spring事务的可能踩的坑:

1. Spring事务未生效

  • 调用方法本身未正确配置事务
  • 本类方法直接调用
  • 数据库操作未通过Spring的DataSourceUtils获取Connection
  • 多线程调用

    2. Spring事务回滚失效

  • 未准确配置rollback-for属性

  • 异常类不属于RuntimeException与Error
  • 应用捕获了异常未抛出

    3. Spring事务超时不准确或失效

  • 超时发生在最后一次JdbcTemplate操作之后

  • 通过非JdbcTemplate操作数据库,例如Mybatis