@Transactional详解
@Transactional 是 Spring 框架中的一个注解,用于声明一个方法需要进行事务处理。通过在方法上加上 @Transactional 注解,表示该方法需要被包装成一个事务,并由 Spring 容器统一管理、控制和回滚事务。@Transactional 注解可以用于类或方法级别上,并且可以指定特定的事务属性,如传播行为、隔离级别、是否只读等。也可以使用默认值,即使用默认的事务管理器和默
Spring 事务的两种使用方式:声明式事务和编程式事务,以及其使用误区和最佳实践。Spring 事务管理的核心就是 @Transactional 注解,它可以用于类或方法级别,用于将方法调用批量提交到数据库中。
Spring 5.0版本中 @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 提供了多种传播行为,如:
- REQUIRED:如果当前存在事务,则加入该事务;否则,新建一个事务。
- SUPPORTS:支持当前事务,如果当前没有事务,则不使用事务。
- MANDATORY:强制使用当前事务,如果当前没有事务,则抛出异常。
- REQUIRES_NEW:如果当前存在事务,则挂起该事务,并开启一个新的事务,直到新事务完成;然后恢复之前的事务。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则挂起该事务。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则嵌套事务执行;否则和 REQUIRED 同样处理。
回滚规则
一个事务执行过程中,如果出现异常,那么事务会回滚。以下是回滚规则:
- @Transactional 注解的默认回滚行为对 unchecked 异常(RuntimeException 及其子类)进行回滚。
- 对于 checked 异常,@Transactional 注解默认不回滚。
- 可以使用 rollbackFor 属性来制定需要回滚的异常类型,如:@Transactional(rollbackFor = Exception.class)。
- noRollbackFor 属性可以指定哪些异常不需要回滚。
事务隔离级别
@Transactional 的隔离级别是指多个事务同时执行时,一个事务所读取的数据被其他事务修改的情况下,该事务所采用的处理方式。Spring 支持五种隔离级别,分别是:
- DEFAULT:使用数据库默认的隔离级别。
- READ_UNCOMMITTED:低级别隔离,允许脏读、幻读和不可重复读。
- READ_COMMITTED:只能读取已提交的数据,避免了脏读问题;但可能出现幻读和不可重复读。
- REPEATABLE_READ:保证了一个事务内,多次读取同样的数据结果相同,避免了幻读问题。
- SERIALIZABLE:最高级别的隔离,将事务串行化执行,性能最低,但可以避免所有的并发问题。
超时设置
@Transactional 中 timeout 属性用于指定事务执行的超时时间(单位为秒),如果方法执行时间超出该值,则自动回滚事务。
@Transactional播机制的使用示例
@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传播行为以非事务方式执行操作,以避免无谓的事务开销和死锁等问题。
@Transactional回滚规则的使用示例
@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类,并指定当该类异常被抛出时不回滚事务。
@Transactional事务隔离级别使用示例
@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事务隔离级别,以确保在查询操作期间数据一直保持一致性。如果其他的事务对表的数据进行了修改,该方法将被阻塞,直到其他的事务提交完成或回滚为止。
根据具体业务场景,灵活选择合适的事务隔离级别可以提高系统性能和数据一致性,但是过高的隔离级别也会带来并发性能的下降。需要根据实际情况进行权衡和选择。
@Transactional声明式事务不生效的几种情况
事务不生效问题经常出现在我们使用 Spring 声明式事务中,主要原因是因为对 Spring 事务管理机制和 声明式事务实现原理不够了解所致,下面我们一起来看下常见误区:
1:类内部访问
- 场景描述:
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) 注解
2:非 public 可重载方法
- 场景描述:
在 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 的并且是可被重载的。
3:异常不匹配(重点)
- 场景描述:
@Transactional 没有设置 rollbackFor = Exception.class 属性或者指定异常类型和方法抛出的异常类型不匹配。
- 举例说明:
@Service
public class TestService {
@Transactional
public void insert() throws Exception {
// 数据库操作
throw new Exception("发生异常");
}
}
- 原因解释:
Spring 事务默认只捕获 uncheck exception,即只会回滚 RuntimeException(运行时异常)和Error(错误),对于普通的 Exception(非运行时异常),它不会回滚。
4:多线程
- 场景描述:
在 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 动态代理,在方法开始是开启一个事务,并把该事务放在当前前程上下文中进行传递,开启子线程事务无法传递,导致子线程事务失效。
5:嵌套事务未捕获异常
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 异常");
}
}
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果。嵌套事务执行异常,自动回滚到保存点,在外部事务捕获异常,做其他逻辑处理。
6:自己吞掉了异常
- 场景描述:
在事务方法中 try…catch 住异常没有向外抛出,导致事务不生效。
- 举例说明:
@Transactional
protected void test(){
// 数据库操作
try {
doOther();
} catch (Exception e) {
log.error("xxx");
}
}
- 原因解释:
Spring 事物处理是通过 AOP try…catch 代理方法,根据异常信息来回滚事物的,如果没检测到异常,也便不会回滚事物了。
@Transactional声明式事务的大事务问题及如何避免大事务
@Transactional注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。
大事务常见问题
所谓大事务就是事务流程较长的事务,大事务会造成许多性能问题:
- 死锁
- 锁定太多的数据,造成大量的阻塞和锁超时
- 执行时间长,容易造成主从延迟
- 回滚时间长
- 并发情况下,数据库连接池容易被占满
- undo log日志膨胀,不仅增加了存储的空间,而且可能降低查询的性能
最主要的影响数据库连接池容易被撑爆,导致大量线程等待,造成请求无响应或请求超时。
如何查询大事务?
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>10
1.少用 @Transactional 注解
@Transactional注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。可以使用我们上面介绍到的编程式事务,在spring项目中使用 TransactionTemplate 手动执行事务。
2. 将查询方法放到事务外
如果出现大事务,可以将查询(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();
}
}
});
}
3. 事务中避免远程调用
我们在接口中调用其他系统的接口是不能避免的,由于网络不稳定,这种远程调的响应时间可能比较长,如果远程调用的代码放在某个事务中,这个事务就可能是大事务。当然,远程调用不仅仅是指调用接口,还有包括:发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();
}
有些朋友可能会问,远程调用的代码不放在事务中如何保证数据一致性呢?这就需要建立:重试+补偿机制,达到数据最终一致性了。
4. 事务中避免一次性处理太多数据
如果一个事务中需要处理的数据太多,也会造成大事务问题。比如为了操作方便,你可能会一次批量更新1000条数据,这样会导致大量数据锁等待,特别在高并发的系统中问题尤为明显。
@Transactional(rollbackFor = Exception.class)
public void saveList(List<User> userList) {
insert(userList);
}
解决办法是分页处理,1000条数据,分50页,一次只处理20条数据,这样可以大大减少大事务的出现。
public void saveList(List<User> userList) {
Lists.partition(userList, 20)
.forEach(users -> {
transactionTemplate.execute(transactionStatus -> {
return insert(users);
});
});
}
Spring5.0@Transactional底层实现原理
Spring 5.0 中 @Transactional 注解的底层实现原理主要依赖于 AOP(面向切面编程) 和事务管理器。
当一个方法被 @Transactional 注解标记时,Spring 会通过 AOP 动态代理创建一个代理对象,该对象拦截了方法的调用,并在方法执行前后启动和提交/回滚事务。而事务管理器则是负责协同 Spring 和底层数据库连接资源,来确保事务完成性与一致性。
具体来说,@Transactional 注解通过 AOP 实现,主要分为以下三个步骤:
- 扫描注解:Spring 在启动时扫描所有带有 @Transactional 注解的类和方法。
- 创建代理对象:对于 @Transactional 注解标记的方法,使用 JDK 代理或 CGLIB 创建一个 AOP 代理对象。
- 织入逻辑:将事务管理器的逻辑代码和 AOP 代理对象织入到目标方法中,在方法开始之前开启事务,在方法正常结束后提交事务,在发生异常时回滚事务。
在以上三个步骤中,第三个步骤是最关键的,因为它是通过 Spring AOP 拦截器来实现的。Spring AOP 拦截器是通过代理对象织入到目标方法中的,从而实现对目标方法的拦截和增强。对于 @Transactional 注解,Spring 提供了一个名为 TransactionInterceptor 的拦截器,该拦截器实现了事务管理器的逻辑代码,用于开启、提交或回滚事务。
具体来说,在 TransactionInterceptor 中,会在目标方法开始之前创建一个事务上下文,并使用当前线程作为该事务的唯一标识。当目标方法执行完毕后,如果没有抛出异常,则提交事务,否则回滚事务。除此之外,TransactionInterceptor 还支持基于注解和编程式指定事务的传播机制、隔离级别、只读属性、超时时间等参数,从而使得事务的管理更加灵活和精细。
综上所述,@Transactional 注解的底层实现原理主要依赖于 AOP 和事务管理器。通过 AOP 创建代理对象、织入事务管理器的逻辑代码,并在目标方法执行前后启动和提交/回滚事务,从而实现对 Spring 事务的控制。
Spring5.0@Transactional注解TransactionInterceptor 拦截器源码逻辑解读
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 拦截器的源码逻辑
创建事务上下文
在目标方法执行前,TransactionInterceptor 会调用 TransactionAspectSupport 类的 beforeTransactionExecution() 方法创建一个新的事务上下文,并将其保存到当前线程的 ThreadLocal 中。该事务上下文包含了当前事务所需要的各种参数,例如传播机制、隔离级别、只读属性、超时时间等等。
开启事务
在创建事务上下文之后,TransactionInterceptor 会通过 PlatformTransactionManager 接口调用底层的事务管理器开启一个新的事务,并将该事务与当前事务上下文关联起来。
执行目标方法
在事务开启之后,TransactionInterceptor 会调用 MethodInvocation 的 proceed() 方法执行目标方法的代码逻辑。在目标方法执行过程中,如果出现了异常,则会在目标方法的抛出点处终止执行,并将异常信息传递给后续步骤进行处理。
提交或回滚事务
在目标方法执行完毕之后,TransactionInterceptor 会判断事务是否需要回滚。如果发生异常,并且该异常被定义为需要回滚的异常,则会回滚事务;否则,会提交事务。在提交或回滚事务之后,TransactionInterceptor 会调用 TransactionAspectSupport 类的 afterTransactionExecution() 方法结束当前事务,并将当前事务上下文从当前线程的 ThreadLocal 中清除。
异常处理
在执行目标方法的过程中,如果出现了异常,则 TransactionInterceptor 会根据事务的传播机制和回滚规则进行相应的处理。具体来说,TransactionInterceptor 会根据事务的传播机制判断该异常是应该被抛到上一级调用者,还是在当前方法内部进行回滚;同时,也会根据回滚规则判断该异常是否需要回滚事务。
总结来说,TransactionInterceptor 拦截器的源码逻辑主要包括创建事务上下文、开启事务、执行目标方法、提交或回滚事务以及异常处理等步骤。通过这些步骤的组合,TransactionInterceptor 实现了对目标方法的事务控制和异常处理,从而保证了事务的完成性与一致性。
更多推荐
所有评论(0)