Spring @Transactional注解失效的情况及解决方法

news/2024/12/28 11:21:24/

Spring框架中的@Transactional注解是非常常用的注解,它可以将一个方法标记为事务性方法,使得在方法执行过程中发生异常时,可以自动回滚事务,保证数据的一致性。然而,在某些情况下,@Transactional注解可能会失效,本文将会介绍这些情况及其解决方法。

一、@Transactional注解的原理

在介绍@Transactional注解失效的情况之前,我们先来了解一下@Transactional注解的原理。

@Transactional注解的实现是基于Spring框架中的AOP(面向切面编程)机制,它通过代理模式在运行时动态地为标记了@Transactional注解的方法生成一个代理对象,这个代理对象会拦截方法的调用,并在方法执行前后开启和提交事务。

具体来说,当一个标记了@Transactional注解的方法被调用时,Spring框架会在运行时为这个方法生成一个代理对象,这个代理对象会在方法执行前开启一个新的事务,并将这个事务与当前线程绑定。当方法执行完成后,代理对象会根据方法的执行结果决定是提交事务还是回滚事务,并将事务与当前线程解绑。

需要注意的是,@Transactional注解只对public方法有效,对于private、protected或者默认访问级别的方法是不起作用的。

二、@Transactional注解失效的情况

虽然@Transactional注解是非常方便的,但是在某些情况下,它可能会失效。下面我们将介绍一些常见的情况。

1. 方法内部调用

如果在一个标记了@Transactional注解的方法内部调用另一个标记了@Transactional注解的方法,那么内部方法的事务将会失效。这是因为Spring框架默认使用基于代理的AOP实现,而代理对象的调用是通过Java反射机制实现的,因此在一个方法内部调用另一个方法时,实际上是在同一个对象中调用方法,而不是通过代理对象调用方法,因此事务无法生效。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);// 调用另一个标记了@Transactional注解的方法updateUserScore(user.getId(), 100);}@Transactionalpublic void updateUserScore(Long userId, int score) {// 更新用户积分userDao.updateUserScore(userId, score);}
}

上面的代码中,updateUser方法和updateUserScore方法都标记了@Transactional注解,但是当updateUser方法调用updateUserScore方法时,updateUserScore方法的事务将会失效。

2. 异常被捕获

如果一个标记了@Transactional注解的方法抛出了异常,并且这个异常被捕获了,那么事务将不会回滚。这是因为Spring框架默认只对未捕获的异常进行回滚,如果异常被捕获了,那么Spring框架就认为这个异常已经被处理了,不需要回滚事务。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);try {// 抛出异常throw new RuntimeException("updateUserScore error");} catch (Exception e) {// 异常被捕获e.printStackTrace();}}
}

上面的代码中,updateUser方法标记了@Transactional注解,并且在方法中抛出了一个RuntimeException异常,并且这个异常被捕获了。在这种情况下,即使抛出了异常,事务也不会回滚。

3. 非public方法

如果一个标记了@Transactional注解的方法的访问级别不是public,那么事务将不会生效。这是因为Spring框架默认只对public方法进行代理,对于非public方法不会进行代理,因此事务无法生效。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalvoid updateUser(User user) {// 更新用户信息userDao.updateUser(user);}
}

上面的代码中,updateUser方法标记了@Transactional注解,但是它的访问级别不是public,因此事务无法生效。

4. 自调用

如果一个标记了@Transactional注解的方法自己调用自己,那么事务将不会生效。这是因为Spring框架默认使用基于代理的AOP实现,而代理对象的调用是通过Java反射机制实现的,因此在一个方法内部调用自己时,实际上是在同一个对象中调用方法,而不是通过代理对象调用方法,因此事务无法生效。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);// 自调用updateUser(user);}
}

上面的代码中,updateUser方法标记了@Transactional注解,并且在方法内部自调用了自己。在这种情况下,事务将不会生效。

5. 异常类型不匹配

如果一个标记了@Transactional注解的方法抛出了一个不是RuntimeException或Error的异常,那么事务将不会回滚。这是因为Spring框架默认只对RuntimeException和Error类型的异常进行回滚,对于其他类型的异常不会进行回滚。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) throws Exception {// 更新用户信息userDao.updateUser(user);// 抛出异常throw new Exception("updateUser error");}
}

上面的代码中,updateUser方法标记了@Transactional注解,并且抛出了一个Exception异常,而不是RuntimeException或Error异常。在这种情况下,事务将不会回滚。

三、@Transactional注解失效的解决方法

针对上面介绍的@Transactional注解失效的情况,我们可以采取以下的解决方法。

1. 方法内部调用

如果在一个标记了@Transactional注解的方法内部调用另一个标记了@Transactional注解的方法,我们可以通过将内部方法抽取到一个单独的类中,并在这个类上标记@Transactional注解来解决这个问题。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Autowiredprivate UserScoreService userScoreService;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);// 调用另一个标记了@Transactional注解的方法userScoreService.updateUserScore(user.getId(), 100);}
}@Service
public class UserScoreServiceImpl implements UserScoreService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUserScore(Long userId, int score) {// 更新用户积分userDao.updateUserScore(userId, score);}
}

上面的代码中,我们将updateUserScore方法抽取到了一个单独的类UserScoreServiceImpl中,并在这个类上标记了@Transactional注解,这样在updateUser方法中调用userScoreService.updateUserScore方法时,事务就能够生效了。

2. 异常被捕获

如果一个标记了@Transactional注解的方法抛出了异常,并且这个异常被捕获了,我们可以通过在catch块中重新抛出异常来解决这个问题。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);try {// 抛出异常throw new RuntimeException("updateUserScore error");} catch (Exception e) {// 异常被捕获e.printStackTrace();// 重新抛出异常throw e;}}
}

上面的代码中,在catch块中重新抛出了异常,这样事务就能够回滚了。

3. 非public方法

如果一个标记了@Transactional注解的方法的访问级别不是public,我们可以将它的访问级别改为public来解决这个问题。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);}
}

上面的代码中,我们将updateUser方法的访问级别改为了public,这样事务就能够生效了。

4. 自调用

如果一个标记了@Transactional注解的方法自己调用自己,我们可以将自调用的逻辑抽取到一个单独的方法中,并在这个方法上标记@Transactional注解来解决这个问题。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void updateUser(User user) {// 更新用户信息userDao.updateUser(user);// 调用自己updateUserInternal(user);}@Transactionalprivate void updateUserInternal(User user) {// 更新用户信息userDao.updateUser(user);}
}

上面的代码中,我们将自调用的逻辑抽取到了一个单独的方法updateUserInternal中,并在这个方法上标记了@Transactional注解,这样在updateUser方法中调用updateUserInternal方法时,事务就能够生效了。

5. 异常类型不匹配

如果一个标记了@Transactional注解的方法抛出了一个不是RuntimeException或Error的异常,我们可以通过在@Transactional注解中指定回滚的异常类型来解决这个问题。

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Transactional(rollbackFor = Exception.class)public void updateUser(User user) throws Exception {// 更新用户信息userDao.updateUser(user);// 抛出异常throw new Exception("updateUser error");}
}

上面的代码中,在@Transactional注解中指定了rollbackFor属性为Exception.class,这样即使抛出了Exception异常,事务也能够回滚。

四、总结

@Transactional注解是Spring框架中非常常用的注解,它可以将一个方法标记为事务性方法,使得在方法执行过程中发生异常时,可以自动回滚事务,保证数据的一致性。然而,在某些情况下,@Transactional注解可能会失效,本文介绍了这些情况及其解决方法。

如果在一个标记了@Transactional注解的方法内部调用另一个标记了@Transactional注解的方法,我们可以将内部方法抽取到一个单独的类中,并在这个类上标记@Transactional注解来解决这个问题;如果一个标记了@Transactional注解的方法抛出了异常,并且这个异常被捕获了,我们可以通过在catch块中重新抛出异常来解决这个问题;如果一个标记了@Transactional注解的方法的访问级别不是public,我们可以将它的访问级别改为public来解决这个问题;如果一个标记了@Transactional注解的方法自己调用自己,我们可以将自调用的逻辑抽取到一个单独的方法中,并在这个方法上标记@Transactional注解来解决这个问题;如果一个标记了@Transactional注解的方法抛出了一个不是RuntimeException或Error的异常,我们可以通过在@Transactional注解中指定回滚的异常类型来解决这个问题。

最后,需要注意的是,虽然@Transactional注解非常方便,但是在使用时需要注意它的失效情况,以免出现数据不一致的情况。

公众号请关注"果酱桑", 一起学习,一起进步!


http://www.ppmy.cn/news/584061.html

相关文章

C++primer(第五版)第二章(变量和基本类型)

2.1基本内置类型 2.1.1算术类型 C的基本内置类型包括算数类型和空类型,空类型就是void,算术类型我从原书中截下来放在下面: 从上表我们可以得知C规定int至少和short一样大,long至少和int一样大,longlong至少和long一样大. 其中char(字符)类型支持国际化,所以char会确保可以放…

23vue3铺垫知识——ES6模块化与异步编程高级用法

文章目录 一、ES6模块化1、回顾:nodejs中如何实现模块化2、前端模块化规范的分类3、什么是ES6模块化规范4、在nodeis中体验ES6模块化5、ES6模块化的基本语法5.1 默认导出与默认导入5.2 按需导出与按需导入5.3直接导入并执行模块中的代码 二、Promise1、回调地狱1.1 如何解决回调…

探索MediaPipe检测人脸关键点

MediaPipe是Google开源的计算机视觉处理框架,基于TensorFlow来训练模型,支持人脸识别、人脸关键点、物体检测追踪、图像分类、人像分割、手势识别、文本分类、语音分类等。我们可以使用CPU来推理,也可以选择GPU加速推理。在滤镜特效场景&…

AI智能服务未来可能的场景

一、产业结构 ChatGPT大模型技术变革加速人工智能产业的变迁 1.投资热 2.产业结构:硬件-云平台-智能应用-应用提供 智能服务产业未来会是一个从算力到服务分发全流程的结构 二、Al智能无处不在的未来,产业将如何演变? 1.技术:…

Docker的“跳过更新”竟要付费;恶意软件用 Rust 重写后更难被发现;15 款输入法被指过多收集用户信息 | 架构视点...

点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复“大礼包”有惊喜礼包! 每日英文 The best way to escape from the past is not to avoid or forget it, but to accept and forgive it. 摆脱过去并不是逃避或者忘记,只是学着去承受…

人工智能标记语言AIML聊天机器人:产生、种类、应用、实例、AIML概述、知识库、公司、业界(20k字经典收藏版)

人工智能标记语言AIML聊天机器人:产生、种类、应用、实例、AIML概述、知识库、公司、业界(20k字经典收藏版) 秦陇纪10译编 聊天机器人(chatterbot)是一个用来模拟人类对话或聊天的程序,试图建立程序让真人…

MySQL中ORDER BY的底层实现原理及示例详解

1. 引言 在MySQL数据库中,ORDER BY是一项常用的功能,用于对查询结果进行排序。本文将详细探讨MySQL中ORDER BY的底层实现原理,涵盖快速排序和归并排序两种排序算法,并包括示例和输出结果的解析。 2. ORDER BY的底层实现原理 OR…

2021年下半年软考信息安全工程师下午案例题及解析

软考信息安全工程师考试中,下午的案例分析是您能否通过考试的关键,案例能通过则整体通过概率就很高,因为从21年考试来看,上午的基础知识考得很简单,下午的案例很难。 和软考文科类考试不一样,信安的案例不是…