spring同类调用事务不生效-原因及三种解决方式
spring提供的聲明式事務注解@Transactional,極大的方便了開發者管理事務,無需手動編寫開啟、提交、回滾事務的代碼。
但是也帶來了一些隱患,如果注解使用不當,可能導致事務不生效,最終導致臟數據也入庫。
如果在同一個類直接調用事務方法,就會導致事務不生效,示例如下
?
public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentMapper studentMapper;@Overridepublic void insertStudent(){insert();}@Transactional(rollbackFor = Exception.class)public void insert() {StudentDO studentDO = new StudentDO();studentDO.setName("小民");studentDO.setAge(22);studentMapper.insert(studentDO);if (studentDO.getAge() > 18) {throw new RuntimeException("年齡不能大于18歲");}} }?
事務不生效的原因在于,spring基于AOP機制實現事務的管理,@Authwired StudentService studentService這樣的方式,調用StudentService的方法時,實際上是通過StudentService的代理類調用StudentService的方法,代理類在執行目標方法前后,加上了事務管理的代碼。
image.png
?
因此,只有通過注入的StudentService調用事務方法,才會走代理類,才會執行事務管理;如果在同類直接調用,沒走代理類,事務就無效。
注意:除了@Transactional,@Async同樣需要代理類調用,異步才會生效
?
但是在實際的業務場景中,同類調用事務方法難以避免,怎么讓同類調用時事務依然生效呢?有以下三個方法
方法一
自己@Autowired自己,示例如下
?
@Service public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate StudentService studentService;@Overridepublic void insertStudent(){studentService.insert();}@Override@Transactional(rollbackFor = Exception.class)public void insert() {StudentDO studentDO = new StudentDO();studentDO.setName("小民");studentDO.setAge(22);studentMapper.insert(studentDO);if (studentDO.getAge() > 18) {throw new RuntimeException("年齡不能大于18歲");}} }可能有人會擔心這樣會有循環依賴的問題,事實上,spring通過三級緩存解決了循環依賴的問題,所以上面的寫法不會有循環依賴問題。
但是!!!,這不代表spring永遠沒有循環依賴的問題(@Async導致循環依賴了解下)
?
方法二
使用AopContext獲取到當前代理類,需要在啟動類加上@EnableAspectJAutoProxy(exposeProxy = true),
示例如下
?
@Service public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentMapper studentMapper;@Overridepublic void insertStudent(){getService().insert();}@Override@Transactional(rollbackFor = Exception.class)public void insert() {StudentDO studentDO = new StudentDO();studentDO.setName("小民");studentDO.setAge(22);studentMapper.insert(studentDO);if (studentDO.getAge() > 18) {throw new RuntimeException("年齡不能大于18歲");}}/*** 通過AopContext獲取代理類* @return StudentService代理類*/private StudentService getService(){return Objects.nonNull(AopContext.currentProxy()) ? (StudentService)AopContext.currentProxy() : this;} }exposeProxy = true用于控制AOP框架公開代理,公開后才可以通過AopContext獲取到當前代理類。(默認情況下不會公開代理,因為會降低性能)
注意:不能保證這種方式一定有效,使用@Async時,本方式可能失效。
(從@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析)
image.png
?
?
方式三(推薦)
通過spring上下文獲取到當前代理類,示例如下
?
@Component public class SpringBeanUtil implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}/*** 通過class獲取Bean* @param clazz class* @param <T> 泛型* @return bean*/public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);} }?
@Service public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentMapper studentMapper;@Overridepublic void insertStudent(){StudentService bean = SpringBeanUtil.getBean(StudentService.class);if (null != bean) {bean.insert();}}@Override@Transactional(rollbackFor = Exception.class)public void insert() {StudentDO studentDO = new StudentDO();studentDO.setName("小民");studentDO.setAge(22);studentMapper.insert(studentDO);if (studentDO.getAge() > 18) {throw new RuntimeException("年齡不能大于18歲");}} }當一個類實現ApplicationContextAware接口后,就可以方便的獲得ApplicationContext中的所有bean。
作者:修行者12138
鏈接:https://www.jianshu.com/p/083605986c8f
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的spring同类调用事务不生效-原因及三种解决方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java序列化接口Serializabl
- 下一篇: 字节跳动Deep Retrieval召回