Spring 事务的两种使用方式:声明式事务和编程式事务,以及其使用误区和最佳实践。Spring 事务管理的核心就是 @Transactional 注解,它可以用于类或方法级别,用于将方法调用批量提交到数据库中。
参数 | 参数意义 | 默认值 | 可选值 |
---|---|---|---|
value 或 transactionManager | 指定使用哪个事务管理器 | "" | 已配置的事务管理器的 bean 名称 |
propagation | 指定事务的传播机制 | REQUIRED | REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED |
isolation | 指定事务的隔离级别 | DEFAULT | DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE |
readOnly | 指定事务是否为只读事务 | false | true 或 false |
timeout | 指定事务的超时时间,以秒为单位 | -1 | 正整数 |
rollbackFor | 指定需要回滚事务的异常类型 | 空数组 | 异常类型 |
noRollbackFor | 指定不需要回滚事务的异常类型 | 空数组 | 异常类型 |
rollbackForClassName | 与 rollbackFor 相同,只不过是用字符串形式指定异常类型 | "" | 异常类型的字符串表示 |
noRollbackForClassName | 与 noRollbackFor 相同,只不过是用字符串形式指定异常类型 | "" | 异常类型的字符串表示 |
@Transactional 可以标记在方法上,也可以标记在接口、类或类上的方法上。当 @Transactional 标注在类上时,该类所有非 private 的方法都具有事务性。
@Transactional 的传播机制是指一个带有事务性质的方法被另外一个方法调用时,当前方法如何使用事务。Spring 提供了多种传播行为,如:
一个事务执行过程中,如果出现异常,那么事务会回滚。以下是回滚规则:
@Transactional 的隔离级别是指多个事务同时执行时,一个事务所读取的数据被其他事务修改的情况下,该事务所采用的处理方式。Spring 支持五种隔离级别,分别是:
@Transactional 中 timeout 属性用于指定事务执行的超时时间(单位为秒),如果方法执行时间超出该值,则自动回滚事务。
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void updateUser(User user){
userDao.update(user);
try {
// 模拟业务异常
int result = 1/0;
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addUser(User user){
userDao.insert(user);
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void getUserById(int id){
userDao.selectById(id);
}
}
在上述示例中,我们定义了一个名为UserServiceImpl
的服务,它使用了@Transactional注解指定了默认的传播行为,并且使用了其他不同的传播行为。
在updateUser()
方法中,我们没有指定传播行为,因此其将使用默认的传播行为REQUIRE。在该方法中,我们做了一个业务操作并进行了异常处理,模拟了一个异常情况。由于默认传播行为下,方法会加入当前事务,因此在出现异常时,事务将进行回滚,以保证数据的一致性和完整性。
在addUser()
方法中,我们使用了REQUIRES_NEW传播行为创建了一个新的事务。在该方法中,我们执行了一个插入操作,因为这个操作需要在一个独立的事务中执行而不受外面事务的影响,所以我们选择使用了REQUIRES_NEW传播行为。
在getUserById()
方法中,我们使用了NOT_SUPPORTED传播行为。在该方法中,我们执行了一个查询操作,并且由于该方法并不需要事务支持,因此我们选择使用了NOT_SUPPORTED传播行为以非事务方式执行操作,以避免无谓的事务开销和死锁等问题。
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void updateUser(User user){
userDao.update(user);
try {
// 模拟业务异常
int result = 1/0;
} catch (Exception e) {
// 回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = RuntimeException.class)
public void addUser(User user){
userDao.insert(user);
}
@Override
@Transactional(noRollbackFor = {CustomException.class})
public void getUserById(int id) throws CustomException {
User user = userDao.selectById(id);
if(user == null){
throw new CustomException("用户不存在");
}
}
}
在上述示例中,我们定义了一个名为UserServiceImpl
的服务,它使用了@Transactional注解进行事务管理,并且使用了不同的回滚规则。
在updateUser()
方法中,我们没有指定回滚规则,但是由于在方法中捕获了RuntimeException异常并手动设置了回滚标志,因此当该方法执行时,即使默认的回滚规则只针对RuntimeException异常生效,它也会被回滚。
在addUser()
方法中,我们使用了REQUIRES_NEW传播机制创建了一个新的事务,并且指定了rollbackFor属性,用于指定此方法抛出指定类型的异常时,回滚事务。在此示例中,我们指定了RuntimeException.class。
在getUserById()
方法中,我们使用了NO_ROLLBACK_FOR传播机制,用于指定不回滚指定类型的异常。在本示例中,我们定义了一个CustomException类,并指定当该类异常被抛出时不回滚事务。
@Service
@Transactional(isolation = Isolation.READ_COMMITTED)
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void updateUser(User user){
userDao.update(user);
}
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void addUser(User user){
userDao.insert(user);
}
@Override
@Transactional(isolation = Isolation.SERIALIZABLE)
public void getUserById(int id){
userDao.selectById(id);
}
}
在上述示例中,我们定义了一个名为UserServiceImpl
的服务,它使用了@Transactional注解指定了默认的事务隔离级别为READ_COMMITTED,并且在其他方法中使用了不同的事务隔离级别。
在updateUser()
方法中,我们没有指定事务隔离级别,因此它将使用默认的READ_COMMITTED级别。在该方法中,我们执行了一个更新操作,事务即时提交。
在addUser()
方法中,我们使用了REPEATABLE_READ事务隔离级别,以确保在插入操作期间读取到的数据一直保持一致性。如果另一个事务已经对数据进行了修改,该操作将会被阻塞,直到前一事务完成。
在getUserById()
方法中,我们使用了SERIALIZABLE事务隔离级别,以确保在查询操作期间数据一直保持一致性。如果其他的事务对表的数据进行了修改,该方法将被阻塞,直到其他的事务提交完成或回滚为止。
根据具体业务场景,灵活选择合适的事务隔离级别可以提高系统性能和数据一致性,但是过高的隔离级别也会带来并发性能的下降。需要根据实际情况进行权衡和选择。
事务不生效问题经常出现在我们使用 Spring 声明式事务中,主要原因是因为对 Spring 事务管理机制和 声明式事务实现原理不够了解所致,下面我们一起来看下常见误区:
A 类的 a1 方法没有标注 @Transactional,a2 方法标注 @Transactional,在 a1里面调用 a2;事务不生效。
@Service
public class TestService {
public void test(){
insert();
}
@Transactional(rollbackFor = Exception.class)
public void insert(){
// 数据库操作
}
}
在test() 方法中调用被 @Transaction 注解标注的 insert()方法
@RestController
public class TestController {
@Resource
private TestService testService;
@GetMapping("/test")
public void test() {
testService.test();
}
}
当进行调用 testService.test() 时,insert() 方法的事务不会生效。
因为 @Transactional 的工作机制是基于 AOP 实现,AOP 是使用动态代理实现的。如果通过代理调用insert(),通过 AOP 会前后进行增强,增强的逻辑其实就是在 insert() 的前后别加上开启、提交事务的逻辑。
而现在是在 test() 方法中直接调用 this.insert(),this 指向 TestService 的非代理对象,不会经过 AOP 的事务增强操作。也就是类内部调用事务方法,不会通过代理方式访问。
解决方法也很简单,这里介绍一个通用的处理方式,那就是在 本类中获取 Spring IoC 容器,使用依赖查找的方式从容器中直接获取 当前代理对象。如下:
@Service
public class TestService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void test() {
// 从Spring IoC 容器中获取代理类
TestService testService = applicationContext.getBean(TestService.class);
// 调用代理类目标方法
testService.insert();
}
@Transactional(rollbackFor = Exception.class)
public void insert() {
// 数据库操作
System.out.println("insert 数据库操作。。。");
}
}
除了上述方法,我们还可以 通过 AopContext 获取代理对象来调用:
@Service
public class TestService {
public void test() {
// 获取当前类的代理对象
TestService testServiceProxy = ((TestService)AopContext.currentProxy())
// 调用代理类目标方法
testServiceProxy.insert();
}
@Transactional(rollbackFor = Exception.class)
public void insert() {
// 数据库操作
System.out.println("insert 数据库操作。。。");
}
}
注意启动类需要添加 @EnableAspectJAutoProxy(exposeProxy = true) 注解
在 Spring Bean 的非 public 方法上,添加 @Transactional 注解不会生效。
@Service
public class TestService {
@Transactional(rollbackFor = Exception.class)
protected void insert1(){
// 数据库操作
}
@Transactional(rollbackFor = Exception.class)
public final void insert2(){
// 数据库操作
}
@Transactional(rollbackFor = Exception.class)
public static void insert3(){
// 数据库操作
}
}
insert1() 被 protected 修饰符修饰,导致事务不会生效; insert2() 被 final 修饰,导致事务不生效; insert3() 被 static 修饰,导致事务不会生效。
@RestController
public class TestController {
@Resource
private TestService testService;
@GetMapping("/test")
public void test() {
testService.insert1();
testService.insert2();
testService.insert3();
}
}
实际使用时,这三种场景不太容易出现,因为 IDEA 会有提醒: 被 @Transactional 标注的方法必须可被重载。
Spring 要求被代理方法必须是 public 的并且是可被重载的。
@Transactional 没有设置 rollbackFor = Exception.class 属性或者指定异常类型和方法抛出的异常类型不匹配。
@Service
public class TestService {
@Transactional
public void insert() throws Exception {
// 数据库操作
throw new Exception("发生异常");
}
}
Spring 事务默认只捕获 uncheck exception,即只会回滚 RuntimeException(运行时异常)和Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。
在 Spring 事务方法中开启线程操作数据库,不受主线程事务控制。
@Transactional(rollbackFor = Exception.class)
public void insert() {
// 开启子线程
new Thread(() -> {
doDb();
}).start();
// 主线程异常
throw new RuntimeException("父线程异常");
}
父线程抛出线程,事务回滚。因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚。
@Transactional(rollbackFor = Exception.class)
public void insert2() {
// 开启子线程
new Thread(() -> {
doDb();
throw new RuntimeException("子线程异常");
}).start();
}
由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。
Spring事务控制是通过 AOP 动态代理,在方法开始是开启一个事务,并把该事务放在当前前程上下文中进行传递,开启子线程事务无法传递,导致子线程事务失效。
Spring 传播属性中的嵌套事务是通过 JDBC 提供 SavePoint 来实现的。
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 比较容易混淆:
PROPAGATION_REQUIRES_NEW:启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
PROPAGATION_NESTED: 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务.潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 rollback。
一个事务方法A调用另一个嵌套事务方法B,嵌套事务方法A 异常,不希望影响 事务方法B
public class TestService {
@Resource
private TagRepository tagRepository;
// 默认隔离级别
@Transactional(propagation = Propagation.REQUIRED)
public void insert1() {
tagRepository.upsert("insert1", TagType.UNKNOWN);
((TestService) AopContext.currentProxy()).insert2();
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = RuntimeException.class)
public void insert2() {
tagRepository.upsert("insert2", TagType.UNKNOWN);
throw new RuntimeException("insert2 exception");
}
}
这种情况使用了嵌套的内部事务,原本是希望调用 insert2()方法时,如果出现了异常,只回滚insert2()方法里的内容,不回滚 insert1里的内容,即回滚保存点。但实际上,insert1 也回滚了。
因为insert2方法出现了异常,没有手动捕获,会继续往上抛,到外层 insert2 方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
可以将内部嵌套事务放在 try/catch 中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。
// 默认隔离级别
@Transactional(propagation = Propagation.REQUIRED)
public void insert1() {
tagRepository.upsert("insert1", TagType.UNKNOWN);
try {
((TestService) AopContext.currentProxy()).insert2();
} catch (Exception e) {
System.out.println("捕获 insert2 异常");
}
}
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果。嵌套事务执行异常,自动回滚到保存点,在外部事务捕获异常,做其他逻辑处理。
在事务方法中 try…catch 住异常没有向外抛出,导致事务不生效。
@Transactional
protected void test(){
// 数据库操作
try {
doOther();
} catch (Exception e) {
log.error("xxx");
}
}
Spring 事物处理是通过 AOP try…catch 代理方法,根据异常信息来回滚事物的,如果没检测到异常,也便不会回滚事物了。
@Transactional注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。
大事务常见问题
所谓大事务就是事务流程较长的事务,大事务会造成许多性能问题:
最主要的影响数据库连接池容易被撑爆,导致大量线程等待,造成请求无响应或请求超时。
如何查询大事务?
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>10
@Transactional注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。可以使用我们上面介绍到的编程式事务,在spring项目中使用 TransactionTemplate 手动执行事务。
如果出现大事务,可以将查询(select)方法放到事务外,也是比较常用的做法,因为一般情况下这类方法是不需要事务的。如下面代码:
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
queryData1();
queryData2();
addData1();
updateData2();
}
可以将queryData1和queryData2两个查询方法放在事务外执行,将真正需要事务执行的代码才放到事务中,比如:addData1和updateData2方法,这样就能有效的减少事务的粒度。
该怎么拆分呢? 多数人可能会想到下面这样:
public void save(User user) {
queryData1();
queryData2();
doSave();
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
这个例子是非常经典的错误,这种直接方法调用的做法事务不会生效,这和我们前面在 Spring 事务不生效一章中介绍的第一种情况一模一样,这种错误经常会在日常开发中出现。即使有经验的开发人员稍不注意,就会采坑。
因此,更推荐使用下面方法重构实现:
@Resource
private TransactionTemplate transactionTemplate;
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
//业务代码
addData1();
updateData2();
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
我们在接口中调用其他系统的接口是不能避免的,由于网络不稳定,这种远程调的响应时间可能比较长,如果远程调用的代码放在某个事务中,这个事务就可能是大事务。当然,远程调用不仅仅是指调用接口,还有包括:发MQ消息,或者连接redis、mongodb保存数据等。
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
callRemoteApi();
addData1();
sendMq();
}
远程调用的代码可能耗时较长,切记一定要放在事务之外。
@Resource
private TransactionTemplate transactionTemplate;
public void save(User user) {
callRemoteApi();
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
//业务代码
addData1();
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
sendMq();
}
有些朋友可能会问,远程调用的代码不放在事务中如何保证数据一致性呢?这就需要建立:重试+补偿机制,达到数据最终一致性了。
如果一个事务中需要处理的数据太多,也会造成大事务问题。比如为了操作方便,你可能会一次批量更新1000条数据,这样会导致大量数据锁等待,特别在高并发的系统中问题尤为明显。
@Transactional(rollbackFor = Exception.class)
public void saveList(List userList) {
insert(userList);
}
解决办法是分页处理,1000条数据,分50页,一次只处理20条数据,这样可以大大减少大事务的出现。
public void saveList(List userList) {
Lists.partition(userList, 20)
.forEach(users -> {
transactionTemplate.execute(transactionStatus -> {
return insert(users);
});
});
}
Spring事务的底层实现流程图
Spring 5.0 中 @Transactional 注解的底层实现原理主要依赖于 AOP(面向切面编程) 和事务管理器。
当一个方法被 @Transactional 注解标记时,Spring 会通过 AOP 动态代理创建一个代理对象,该对象拦截了方法的调用,并在方法执行前后启动和提交/回滚事务。而事务管理器则是负责协同 Spring 和底层数据库连接资源,来确保事务完成性与一致性。
具体来说,@Transactional 注解通过 AOP 实现,主要分为以下三个步骤:
在以上三个步骤中,第三个步骤是最关键的,因为它是通过 Spring AOP 拦截器来实现的。Spring AOP 拦截器是通过代理对象织入到目标方法中的,从而实现对目标方法的拦截和增强。对于 @Transactional 注解,Spring 提供了一个名为 TransactionInterceptor 的拦截器,该拦截器实现了事务管理器的逻辑代码,用于开启、提交或回滚事务。
具体来说,在 TransactionInterceptor 中,会在目标方法开始之前创建一个事务上下文,并使用当前线程作为该事务的唯一标识。当目标方法执行完毕后,如果没有抛出异常,则提交事务,否则回滚事务。除此之外,TransactionInterceptor 还支持基于注解和编程式指定事务的传播机制、隔离级别、只读属性、超时时间等参数,从而使得事务的管理更加灵活和精细。
综上所述,@Transactional 注解的底层实现原理主要依赖于 AOP 和事务管理器。通过 AOP 创建代理对象、织入事务管理器的逻辑代码,并在目标方法执行前后启动和提交/回滚事务,从而实现对 Spring 事务的控制。
Spring 5.0 中的 @Transactional 注解主要使用 TransactionInterceptor 拦截器实现了对目标方法的拦截和增强。下面我们来简单解读一下 TransactionInterceptor 的源码逻辑。
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, BeanFactoryAware {
// BeanFactory 对象
private BeanFactory beanFactory;
// 执行目标方法并返回结果
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 获取目标方法
final Method method = invocation.getMethod();
// 判断是否可以进行事务处理,如果不能则直接执行目标方法
if (!shouldIntercept(method, null)) {
return invocation.proceed();
}
// 获取目标方法所在的类
final Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 获取事务属性
final TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 如果找不到事务属性,则直接执行目标方法
if (txAttr == null) {
return invocation.proceed();
}
// 获取 PlatformTransactionManager 实例
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 创建事务上下文
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 执行目标方法
retVal = invocation.proceed();
}
catch (Throwable ex) {
// 如果出现异常,则将异常信息传递给后续步骤进行处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 完成事务的提交或回滚,并清除当前事务上下文
cleanupTransactionInfo(txInfo);
}
// 完成事务的提交或回滚,并清除当前事务上下文
commitTransactionAfterReturning(txInfo);
return retVal;
}
// 设置 BeanFactory 对象
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
// 获取 PlatformTransactionManager 实例
protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) {
return getTransactionManager();
}
// 创建事务上下文
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, String joinpointIdentification) {
TransactionStatus status = null;
if (txAttr != null) {
// 如果事务属性不为空,则开启一个新的事务
status = tm.getTransaction(txAttr);
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
// 完成事务的提交或回滚,并清除当前事务上下文
protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
if (txInfo != null && txInfo.hasTransaction()) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
// 完成事务的提交或回滚,并清除当前事务上下文
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.hasTransaction()) {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
}
// 清除当前事务上下文
protected void cleanupTransactionInfo(TransactionInfo txInfo) {
if (txInfo != null) {
txInfo.restoreThreadLocalStatus();
}
}
}
TransactionInterceptor 拦截器主要实现了 MethodInterceptor 接口,并重写了 invoke() 方法。在 invoke() 方法中,TransactionInterceptor 会先获取目标方法和目标方法所在类的信息,然后判断是否需要进行事务处理。如果不需要,则直接执行目标方法;否则,就根据获取到的事务属性创建一个新的事务上下文,并开启一个新的事务。
在执行目标方法的过程中,如果出现异常,则 TransactionInterceptor 会通过 completeTransactionAfterThrowing() 方法回滚当前事务,并将异常信息传递给后续步骤进行处理。如果执行正常,则 TransactionInterceptor 会通过 commitTransactionAfterReturning() 方法提交当前事务。
在目标方法执行完毕之后,TransactionInterceptor 还会通过 cleanupTransactionInfo() 方法清除当前事务上下文,从而释放资源。
创建事务上下文
在目标方法执行前,TransactionInterceptor 会调用 TransactionAspectSupport 类的 beforeTransactionExecution() 方法创建一个新的事务上下文,并将其保存到当前线程的 ThreadLocal 中。该事务上下文包含了当前事务所需要的各种参数,例如传播机制、隔离级别、只读属性、超时时间等等。
开启事务
在创建事务上下文之后,TransactionInterceptor 会通过 PlatformTransactionManager 接口调用底层的事务管理器开启一个新的事务,并将该事务与当前事务上下文关联起来。
执行目标方法
在事务开启之后,TransactionInterceptor 会调用 MethodInvocation 的 proceed() 方法执行目标方法的代码逻辑。在目标方法执行过程中,如果出现了异常,则会在目标方法的抛出点处终止执行,并将异常信息传递给后续步骤进行处理。
提交或回滚事务
在目标方法执行完毕之后,TransactionInterceptor 会判断事务是否需要回滚。如果发生异常,并且该异常被定义为需要回滚的异常,则会回滚事务;否则,会提交事务。在提交或回滚事务之后,TransactionInterceptor 会调用 TransactionAspectSupport 类的 afterTransactionExecution() 方法结束当前事务,并将当前事务上下文从当前线程的 ThreadLocal 中清除。
异常处理
在执行目标方法的过程中,如果出现了异常,则 TransactionInterceptor 会根据事务的传播机制和回滚规则进行相应的处理。具体来说,TransactionInterceptor 会根据事务的传播机制判断该异常是应该被抛到上一级调用者,还是在当前方法内部进行回滚;同时,也会根据回滚规则判断该异常是否需要回滚事务。
总结来说,TransactionInterceptor 拦截器的源码逻辑主要包括创建事务上下文、开启事务、执行目标方法、提交或回滚事务以及异常处理等步骤。通过这些步骤的组合,TransactionInterceptor 实现了对目标方法的事务控制和异常处理,从而保证了事务的完成性与一致性。