聊聊@Transactional注解和事务的使用

@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。

一、事务

事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为**编程式事务声明式事务**两种。

1.编程式事务

是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。

编程式事务方式需要开发者在代码中手动的管理事务的开启、提交、回滚等操作。

package com.hanserwei.transactiondemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.sql.DataSource;

@Component
public class TransactionDemo {
    @Autowired
    private DataSource dataSource;

    public void demo() {
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            // 业务逻辑1

            // 业务逻辑2

            // 业务逻辑3

            // 没有异常,提交事务
            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            // 出现异常,回滚事务
            transactionManager.rollback(transactionStatus);
        }
    }
}

如以上代码,开发者可以通过API自己控制事务。

2.声明式事务

基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TXAOP的xml配置文件方式,二种就是基于@Transactional注解了。

声明式事务管理方法允许开发者配置的帮助下来管理事务,而不需要依赖底层API进行硬编码。开发者可以只使用注解或基于配置的 XML 来管理事务。

在配置类开启事务选项

/**
 * Spring核心配置类
 * 用于配置Spring容器的组件扫描、属性源、数据库配置导入和事务管理
 */
@Configuration
@ComponentScan(basePackages = {"com.hanserwei"}) // 配置组件扫描包路径
@PropertySource({"classpath:dao/db.properties"}) // 加载数据库配置文件
@Import({DruidConfig.class, MyBatisConfig.class}) // 导入Druid数据源配置和MyBatis配置
@EnableTransactionManagement() // 开启事务管理功能
public class SpringConfig {
}

然后使用注解即可!

@Transactional
public void demo2() {
// 业务逻辑1

// 业务逻辑2

// 业务逻辑3
}

Note that it does not apply to ancestor classes up the class hierarchy!

这个是官方给出的,不要加在它的祖宗类上,也就是说如果你要对一个接口所有方法事务管理,请加在该接口的实现类上,而不是加载该接口上面。

声明式事务对代码没有侵入性,方法内只需要写业务逻辑就可以了,帮助我们节省了很多代码,他会自动帮我们进行事务的开启、提交以及回滚等操作,把程序员从事务管理中解放出来。可以说优点很明显,但是这种方式的缺点也很明显。

问题一:声明式事务的粒度问题

首先,*声明式事务有一个局限,那就是他的最小粒度要作用在方法上。*

也就是说,如果想要给一部分代码块增加事务的话,那就需要把这个部分代码块单独独立出来作为一个方法。

在大事务耗时过长需要高并发优化的情况下不建议使用声明式事务,建议采用上面提到的编程式事务方式。

问题二:声明式事务容易被开发者忽略

而事务一旦被忽略就容易造成很多故障

首先,如果开发者没有注意到一个方法是被事务嵌套的,那么就可能会再方法中加入一些如RPC远程调用、消息发送、缓存更新、文件写入等操作。

我们知道,这些操作如果被包在事务中,有两个问题:

1、这些操作自身是无法回滚的,这就会导致数据的不一致。可能RPC调用成功了,但是本地事务回滚了,可是PRC调用无法回滚了(这里不讨论分布式事务)。

2、在事务中有远程调用,就会拉长整个事务。那么久会导致本事务的数据库连接一直被占用,那么如果类似操作过多,就会导致数据库连接池耗尽。

有些时候,即使没有在事务中进行远程操作,但是有些人还是可能会不经意的进行一些内存操作,如运算。或者如果遇到分库分表的情况,有可能不经意间进行跨库操作。

问题三:声明式事务用不对在某些场景下容易失效

这个问题后面我们着重讲述一下失效的几种场景。

二、@Transactional介绍

1、@Transactional 可以作用在接口类方法

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。

  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效。(反正别用,除非你强制开启CGLIB代理)

2、@Transactional 有哪些属性

属性

类型

描述

value

String

可选的限定描述符,指定使用的事务管理器

propagation

enum: Propagation

可选的事务传播行为设置

isolation

enum: Isolation

可选的事务隔离级别设置

readOnly

boolean

读写或只读事务,默认读写

timeout

int (in seconds granularity)

事务超时时间设置

rollbackFor

Class对象数组,必须继承自Throwable

导致事务回滚的异常类数组

rollbackForClassName

类名数组,必须继承自Throwable

导致事务回滚的异常类名字数组

noRollbackFor

Class对象数组,必须继承自Throwable

不会导致事务回滚的异常类数组

noRollbackForClassName

类名数组,必须继承自Throwable

不会导致事务回滚的异常类名字数组

propagation属性

propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )

  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED :和Propagation.REQUIRED效果一样。

isolation 属性

isolation :事务的隔离级别,默认值为 Isolation.DEFAULT

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别。

  • Isolation.READ_UNCOMMITTED

  • Isolation.READ_COMMITTED

  • Isolation.REPEATABLE_READ

  • Isolation.SERIALIZABLE

timeout 属性

timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

@Override
@Transactional(propagation = Propagation.REQUIRED, timeout = 5)
public void addUser(User user) {
    System.out.println("---- UserServiceImpl  addUser方法开始 ");
    usermapper.addUser(user);
    try {
        Thread.sleep(7000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("---- UserServiceImpl  addUser方法结束 ");
}

似乎这样貌似在最新版本的spring里面不行,其实可以更简单方法,我们直接在数据库层面动手。

package com.hanserwei.mapper;

import com.hanserwei.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserMapper {
    @Update("SELECT SLEEP(10) FROM user")
    void addUser(User user);
}

这里我直接使用MySQL内置的函数SLEEP,让它在数据库睡10秒。这样就可以完美触发报错!

Exception in thread "main" org.springframework.dao.QueryTimeoutException: 

最终,由于事务超时,事务回滚。

readOnly 属性

readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性

noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

三、@Transactional失效的场景

接下来我们结合具体的代码分析一下哪些场景下,@Transactional 注解会失效。

1、@Transactional 应用在非 public 修饰的方法上

如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。

sequenceDiagram participant Target Class participant CglibAopProxy$DynamicAdviseInterceptor participant TransactionInterceptor participant AbstractPlatformTransactionManager participant DataSource Target Class->>CglibAopProxy$DynamicAdviseInterceptor: call target method() CglibAopProxy$DynamicAdviseInterceptor->>TransactionInterceptor: intercept() TransactionInterceptor->>TransactionInterceptor: invokeWithinTransaction() alt Success TransactionInterceptor->>TransactionInterceptor: commitTransactionAfterReturning() TransactionInterceptor->>AbstractPlatformTransactionManager: commit() AbstractPlatformTransactionManager->>AbstractPlatformTransactionManager: processCommit() AbstractPlatformTransactionManager->>DataSource: commit() else Failure TransactionInterceptor->>TransactionInterceptor: completeTransactionAfterThrowing() TransactionInterceptor->>AbstractPlatformTransactionManager: rollback() AbstractPlatformTransactionManager->>AbstractPlatformTransactionManager: processRollback() AbstractPlatformTransactionManager->>DataSource: rollback() end

在这里插入图片描述

之所以会失效是因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptorCglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

	/**
	 * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
	 * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
	 * <p>As of 4.1.8, this method can be overridden.
	 * @since 4.1.8
	 * @see #getTransactionAttribute
	 */
	@Nullable
	protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
		// 关键在这,过滤掉非public的方法
		// Don't allow non-public methods, as configured.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		// First try is the method in the target class.
		TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
		if (txAttr != null) {
			return txAttr;
		}

		// Second try is the transaction attribute on the target class.
		txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
		if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
			return txAttr;
		}

		if (specificMethod != method) {
			// Fallback is to look at the original method.
			txAttr = findTransactionAttribute(method);
			if (txAttr != null) {
				return txAttr;
			}
			// Last fallback is the class of the original method.
			txAttr = findTransactionAttribute(method.getDeclaringClass());
			if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
				return txAttr;
			}
		}

		return null;
	}

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

注意:protectedprivate 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

2、@Transactional 注解属性 propagation 设置错误

这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

    我这里来模拟一下这个操作!

    首先我定义一个保存日志的业务类:

    package com.hanserwei.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class LogService {
        @Autowired
        private LogRepository logRepository;
    
        // 错误配置:该方法不应该在事务中执行
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public <T> void log(T t) {
            logRepository.log(t.toString());
            // 模拟异常
            throw new RuntimeException();
            
        }
    }
    
    

    保存日志的数据访问层

    package com.hanserwei.mapper;
    
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface LogRepository {
        @Insert("insert into log(id,log) values(null ,#{log} )")
        void log(String log);
    }
    
    
    

    然后我在插入用户的时候同时调用日志业务保存日志:

    package com.hanserwei.service.impl;
    
    import com.hanserwei.entity.User;
    import com.hanserwei.mapper.UserMapper;
    import com.hanserwei.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper usermapper;
    
        @Autowired
        private LogService logService;
    
        @Override
        @Transactional
        public void addUser(User user) {
            usermapper.addUser(user);
            logService.log(user);
        }
    
    }
    
    

    测试类:

    package com.hanserwei.test;
    
    import com.hanserwei.config.SpringConfig;
    import com.hanserwei.controller.UserController;
    import com.hanserwei.entity.User;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class UserControllerTest {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
            UserController userController = applicationContext.getBean(UserController.class);
            User user = new User();
            user.setName("Hanserwei");
            userController.addUser(user);
        }
    }
    
    

    运行结果:

    image-20250828185613061

    再看看数据库:

    image-20250828191503237

    image-20250828191552800

    可以看到,日志插入了,但是用户没有插入!我来分析一下这个原因:

    1. addUser 方法开启事务UserServiceImpl.addUser() 方法被调用时,@Transactional 注解会启动一个新事务。

    2. usermapper.addUser(user) 操作 usermapper.addUser(user) 在这个事务中执行,将用户数据插入到数据库中。此时,这个操作并未提交。

    3. logService.log(user) 方法调用 addUser 方法接着调用 logService.log()log() 方法上的 @Transactional(propagation = Propagation.NOT_SUPPORTED) 注解会暂停 addUser 方法的事务。

    4. 日志操作在非事务环境中执行 logService.log() 方法在非事务环境中执行了 logRepository.log()。由于没有事务管理,这个日志操作会立即提交到数据库。

    5. 异常的发生与传播 logService.log() 方法接着抛出了一个 RuntimeException。这个异常会传播回 UserServiceImpl.addUser()

    6. addUser 方法事务回滚 addUser 方法捕获到这个 RuntimeException。因为它被 @Transactional 注解修饰,Spring 事务管理器会执行回滚操作,撤销 usermapper.addUser(user) 之前的操作。

  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

    还是刚刚的添加用户和保存日志的操作,做出一点小修改:

    package com.hanserwei.service.impl;
    
    import com.hanserwei.entity.User;
    import com.hanserwei.mapper.UserMapper;
    import com.hanserwei.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper usermapper;
    
        @Autowired
        private LogService logService;
    
        @Override
        // 这是一个没有事务的方法
        public void addUser(User user) {
            usermapper.addUser(user);
            logService.log(user);
        }
    
    }
    
    
    package com.hanserwei.service.impl;
    
    import com.hanserwei.mapper.LogRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class LogService {
        @Autowired
        private LogRepository logRepository;
    
        // 错误配置:如果该方法被非事务方法调用,它将不具备事务能力
        @Transactional(propagation = Propagation.SUPPORTS)
        public <T> void log(T t) {
            logRepository.log(t.toString());
            // 模拟异常
            throw new RuntimeException();
        }
    }
    
    

    运行我们来看结果:

    image-20250828192358850

    异常正常抛出,我们来查看一些数据库

    image-20250828192437419

    image-20250828192454314

    可以看到,由于两个数据库都插入了,事务失效了。原因在于,addUser是一个没有事务的方法,但是它区调用了一个@Transactional(propagation = Propagation.SUPPORTS)标记的方法,这个标记会让log这个方法也加入无事务的环境,所以事务就失效了。

  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

    最后这个例子是最简单的例子,与其说它是事务失效,不如说是spring的一种提醒,提醒你不要在一个已经有事务的环境中执行某个方法。

    package com.hanserwei.service.impl;
    
    import com.hanserwei.entity.User;
    import com.hanserwei.mapper.UserMapper;
    import com.hanserwei.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper usermapper;
    
        @Autowired
        private LogService logService;
    
        @Transactional
        @Override
        public void addUser(User user) {
            usermapper.addUser(user);
            logService.log(user);
        }
    
    }
    
    
    package com.hanserwei.service.impl;
    
    import com.hanserwei.mapper.LogRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class LogService {
        @Autowired
        private LogRepository logRepository;
    
        @Transactional(propagation = Propagation.NEVER)
        public <T> void log(T t) {
            logRepository.log(t.toString());
            // 模拟异常
            throw new RuntimeException();
        }
    }
    
    

    运行直接报错!

    image-20250828193253205

  • Propagation.REQUIRES_NEW当带有 Propagation.REQUIRES_NEW 注解的方法被调用时,Spring 会立即暂停当前正在运行的事务(如果存在的话),然后开启一个新的事务。这个新事务是完全独立的。它的提交和回滚不会影响到外部的那个被暂停的事务。

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void addUserAndAddress(User user, Address address) throws Exception {
        userMapper.save(user);
        addAddress(address);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void addAddress(Address address) {
        addressMapper.save(address);
    }
    

    这里配置了 Propagation.REQUIRES_NEW,这意味着每当调用这两个方法时,都会启动一个新的独立事务。因此,addUserAndAddress 的事务和 addAddress 的事务是完全独立的两个事务,它们之间无法共享数据的一致性。如果其中一个事务失败回滚,另一个事务不会受到影响,这可能导致数据不一致。

3、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

graph LR subgraph 运行时异常 I[RuntimeException] --> L(ArrithmeticException) I --> M(MissingResourceException) I --> N(ClassNotFoundException) I --> O(NullPointerException) I --> P(IllegalArgumentException) I --> Q(ArrayIndexOutOfBoundsException) I --> R(UnknownTypeException) end subgraph 非运行时异常 H[IOException] --> J(EOFException) H --> K(FileNotFoundException) end D[VirtualMachineError] --> F(StackOverflowError) D --> G(OutOfMemoryError) A[Throwable] --> C[Exception] A --> B[Error] C --> H C --> I B --> D B --> E(AWTError) %% 定义样式 classDef redBox fill:#fdd,stroke:#f00,stroke-width:2px; %% 应用样式 class B,I redBox;

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

image-20250828193556075

image-20250828193611451

源码如下:(递归找吗,有意思)

	private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
		if (exceptionToMatch.equals(declaredException)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionToMatch == Throwable.class) {
			return Integer.MAX_VALUE;
		}
		return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
	}

4、同一个类中方法调用,导致@Transactional失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

还是做一下实验:

package com.hanserwei.service.impl;

import com.hanserwei.entity.User;
import com.hanserwei.mapper.UserMapper;
import com.hanserwei.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper usermapper;

    @Autowired
    private LogService logService;

    @Override
    public void addUser(User user) {
        usermapper.addUser(user);
        saveLog(user);
    }

    @Transactional
    protected void saveLog(User user) {
        logService.log(user);
    }

}

异常还是会正常抛出:

image-20250828195202783

但是两个数据库都被插入了,事务失效了!

image-20250828195237167

image-20250828195259051

image-20250828195021538

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。而且我还发现一点,就是说如果要加@Transactional注解,那么该方法必须是可以被重写的,所以私有不行!

5、异常被你的 catch“吃了”导致@Transactional失效

这种情况是最常见的一种@Transactional注解失效场景

直接来试一把:

package com.hanserwei.service.impl;

import com.hanserwei.entity.User;
import com.hanserwei.mapper.UserMapper;
import com.hanserwei.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper usermapper;

    @Autowired
    private LogService logService;

    @Override
    @Transactional
    public void addUser(User user) {
        try {
            usermapper.addUser(user);
            int i = 1 / 0;
        } catch (Exception e) {
            // 抛可以,抛了被spring接住了还是可以回滚
            // throw new RuntimeException(e);
            
            // 打印不行,或者吞掉也不行
            e.printStackTrace();
        }
    }
}

会抛出异常吗?会的。会回滚事务吗?答案是不会!

在业务方法中一般不需要catch异常,如果非要catch一定要手动抛出throw new RuntimeException(),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

6、@Transactional 注解应用于 finalstatic 方法

由于 AOP(面向切面编程)的限制,@Transactional 注解无法应用于 finalstatic 方法。

  • final 方法:Spring Boot 2.x 默认使用 cglib 作为代理,而 cglib 的工作原理是生成一个子类来覆盖目标方法。由于 final 方法无法被子类覆盖,因此无法创建代理,@Transactional 也便无法生效。

  • static 方法:static 方法是属于类的,而不是类的实例。AOP 代理是基于实例的,所以无法对静态方法进行代理。

6、数据库引擎不支持事务

这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

7、多线程环境下也会失效

因为@Transactional 是基于ThreadLocal储存上下文的,你多线程,每个线程都有一个单独的ThreadLocal,自然无法保持事务同步!