java同一個類中,一個無事務(wù)方法調(diào)用一個有事務(wù)方法時,事務(wù)失效問題
事務(wù)的使用
在spring項(xiàng)目的開發(fā)中,通過在方法上添加Transactional注解,實(shí)現(xiàn)事務(wù)的管理,在方法開始開啟事務(wù),出現(xiàn)異常進(jìn)行事務(wù)的回滾,方法結(jié)束前提交事務(wù)。
事務(wù)的實(shí)現(xiàn)原理
Transactional 注解是 Spring 框架用來實(shí)現(xiàn)聲明式事務(wù)管理的重要工具。其原理主要基于 AOP(面向切面編程),通過動態(tài)代理在方法執(zhí)行前、后以及異常情況下進(jìn)行事務(wù)的處理。
當(dāng)你在一個方法上使用 @Transactional 注解時,Spring 會在運(yùn)行時生成一個代理對象,該對象會攔截對該方法的調(diào)用。
在調(diào)用之前,代理會開始一個新的事務(wù);在方法執(zhí)行完成后,代理會提交或回滾事務(wù),具體取決于方法是否拋出了未處理的異常。
具體流程如下:
- spring使用jdk動態(tài)代理技術(shù)或者cglib代理來創(chuàng)建目標(biāo)類的代理對象
- 當(dāng)方法被調(diào)用時,實(shí)際上調(diào)用的是代理類中的增強(qiáng)方法,而不是直接調(diào)用目標(biāo)類中的方法
- 在調(diào)用方法之前,代理會根據(jù)注解的屬性(傳播行為和隔離級別)從
PlatformTransactionManager中獲取一個事務(wù),在調(diào)用目標(biāo)方法前開啟事務(wù) - 執(zhí)行目標(biāo)方法
- 目標(biāo)方法執(zhí)行成功,則提交事務(wù),執(zhí)行失敗,則回滾事務(wù)
代理的兩種方式:
- JDK 動態(tài)代理:當(dāng)目標(biāo)類實(shí)現(xiàn)至少一個接口時,Spring會使用JDK動態(tài)代理。這種代理僅適用于基于接口的代理。原理是通過Java反射機(jī)制,在運(yùn)行時生成一個實(shí)現(xiàn)了目標(biāo)類接口的代理類。
- CGLIB 代理:如果目標(biāo)類沒有實(shí)現(xiàn)任何接口,或者您強(qiáng)制配置為使用CGLIB,那么Spring會使用CGLIB庫生成一個目標(biāo)類的子類作為代理。原理是使用CGLIB庫,通過繼承目標(biāo)類并重寫其方法來實(shí)現(xiàn)代理。
原因
在同一個類中,一個無事務(wù)方法直接調(diào)用有事務(wù)的方法時,是通過this.方法名的方式調(diào)用。
this方式:它直接訪問的是當(dāng)前對象的實(shí)現(xiàn),如果當(dāng)前類被Spring AOP代理,那么使用this調(diào)用的方法將不會觸發(fā)AOP切面。也就是說,切面(如事務(wù)管理、日志記錄等)不會生效,因?yàn)槟阒苯诱{(diào)用了目標(biāo)對象的方法,而不是代理對象的方法。
通過Autowired注解注入的bean進(jìn)行調(diào)用的方式:是通過spring容器管理的代理對象進(jìn)行調(diào)用,這種情況下AOP特性可以正常工作,例如事務(wù)、日志等會生效。
Spring容器管理的代理對象的生成條件和時機(jī)
在Spring中,代理對象的生成通常與AOP(面向切面編程)相關(guān),當(dāng)類上使用@Component、@Service、@Repository或@Controller等注解,并且方法上使用AOP相關(guān)的注解時,Spring會創(chuàng)建代理對象,以便能夠在調(diào)用這些方法時執(zhí)行切面邏輯。
常見的AOP注解包括:
@Transactional@Cacheable@Async@Scheduled
事務(wù)失效代碼
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Author linaibo
* @Date 2024/8/3 15:36
* @Version 1.0
*/
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {
@Autowired
private SysDeptMapper deptMapper;
@Autowired
private SysConfigMapper configMapper;
@Override
public int insertDept(SysDept dept) {
dept.setAncestors("123123");
deptMapper.insertDept(dept);
insertConfig();
return 1;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertConfig() {
SysConfig config;
for (int i = 0; i < 5; i++) {
config = new SysConfig();
config.setConfigName("配置" + i);
configMapper.insertConfig(config);
if (i == 2) {
throw new RuntimeException();
}
}
return 1;
}
}解決方法
①自己autowire自己(也可以將方法放到另外一個service中,然后注入該service進(jìn)行調(diào)用)
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Author linaibo
* @Date 2024/8/3 15:36
* @Version 1.0
*/
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {
@Autowired
private SysDeptMapper deptMapper;
@Autowired
private SysConfigMapper configMapper;
@Autowired
private TestTransactionalService testTransactionalService;
@Override
public int insertDept(SysDept dept) {
dept.setAncestors("123123");
deptMapper.insertDept(dept);
testTransactionalService.insertConfig();
return 1;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertConfig() {
SysConfig config;
for (int i = 0; i < 5; i++) {
config = new SysConfig();
config.setConfigName("配置" + i);
configMapper.insertConfig(config);
if (i == 2) {
throw new RuntimeException();
}
}
return 1;
}
}②通過spring上下文獲取到當(dāng)前代理類
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Author linaibo
* @Date 2024/8/3 15:36
* @Version 1.0
*/
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {
@Autowired
private SysDeptMapper deptMapper;
@Autowired
private SysConfigMapper configMapper;
@Autowired
private TestTransactionalService testTransactionalService;
@Autowired
private ApplicationContext applicationContext;
@Override
public int insertDept(SysDept dept) {
dept.setAncestors("123123");
deptMapper.insertDept(dept);
TestTransactionalService service = applicationContext.getBean(TestTransactionalService.class);
service.insertConfig();
return 1;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertConfig() {
SysConfig config;
for (int i = 0; i < 5; i++) {
config = new SysConfig();
config.setConfigName("配置" + i);
configMapper.insertConfig(config);
if (i == 2) {
throw new RuntimeException();
}
}
return 1;
}
}③使用AopContext獲取到當(dāng)前代理類,需要在啟動類加上EnableAspectJAutoProxy(exposeProxy = true),exposeProxy = true用于控制AOP框架公開代理,公開后才可以通過AopContext獲取到當(dāng)前代理類。
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
/**
* @Author linaibo
* @Date 2024/8/3 15:36
* @Version 1.0
*/
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {
@Autowired
private SysDeptMapper deptMapper;
@Autowired
private SysConfigMapper configMapper;
@Override
public int insertDept(SysDept dept) {
dept.setAncestors("123123");
deptMapper.insertDept(dept);
TestTransactionalService service = Objects.nonNull(AopContext.currentProxy()) ? (TestTransactionalService)AopContext.currentProxy() : this;
service.insertConfig();
return 1;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertConfig() {
SysConfig config;
for (int i = 0; i < 5; i++) {
config = new SysConfig();
config.setConfigName("配置" + i);
configMapper.insertConfig(config);
if (i == 2) {
throw new RuntimeException();
}
}
return 1;
}
}總結(jié)
使用 AOP 注解:
- 如果
TestTransactionalService類上使用了 AOP 相關(guān)的注解(如@Transactional,@Aspect,@Around等) - 通過
applicationContext.getBean(TestTransactionalService.class)獲取的對象將是一個代理對象
代理對象的創(chuàng)建是為了在方法調(diào)用前后加入額外的行為,比如事務(wù)管理、日志記錄等。
未使用 AOP 注解:
- 如果該類沒有任何 AOP 相關(guān)的注解
- 獲取的對象就是普通的 Bean
- 沒有經(jīng)過 AOP 的增強(qiáng)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Elasticsearch Join字段類型簡單快速上手教程
這篇文章主要為大家介紹了Elasticsearch Join字段類型簡單快速上手教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
IDEA在一個項(xiàng)目空間下管理多個項(xiàng)目的操作方法
這篇文章主要介紹了IDEA如何在一個項(xiàng)目空間下管理多個項(xiàng)目,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
SpringBoot快速搭建TCP服務(wù)端和客戶端全過程
這篇文章主要介紹了SpringBoot快速搭建TCP服務(wù)端和客戶端全過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
Java使用connectTo方法提高代碼可續(xù)性詳解
這篇文章主要介紹了Java使用connectTo方法提高代碼可續(xù)性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
MyBatis使用動態(tài)SQL標(biāo)簽的小陷阱
MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架,MyBatis越來越受大家的喜愛了。下面給大家分享MyBatis使用動態(tài)SQL標(biāo)簽的小陷阱,感興趣的朋友一起看看吧2016-10-10
MyBatis-Plus數(shù)據(jù)權(quán)限插件的簡單使用
在MyBatis-Plus中,通過DataPermissionInterceptor插件實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制,首先需要創(chuàng)建自定義注解和處理類,利用JSQLParser庫動態(tài)修改SQL,實(shí)現(xiàn)按角色權(quán)限過濾數(shù)據(jù),配置類中注冊攔截器,確保只有授權(quán)用戶能訪問指定數(shù)據(jù),感興趣的可以了解一下2024-10-10

