項目中遇到的問題的結論,具體現象就不描述了,通過查資料,就是知道一點就行:緩存、事務、異步不能同一個類中相互調用,會失效(只能在別的類里調用)。具體問題描述我們直接看網上的文章吧。
一、同一個類中方法調用,導致@Transactional失效
一、問題現象
開發中避免不了會對同一個類里面的方法調用,比如有一個類Test,它的一個方法A,A再調用本類的方法B(不論方法B是用public還是private修飾),但方法A沒有聲明注解事務,而B方法有。則外部調用方法A之后,方法B的事務是不會起作用的。這也是經常犯錯誤的一個地方:
@GetMapping("/test") private Integer A() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("2"); /** * B 插入字段為 3的數據 */
int insert=insertB(); /** * A 插入字段為 2的數據 */
int insert = cityInfoDictMapper.insert(cityInfoDict); return insert; } @Transactional() public Integer insertB() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("3"); cityInfoDict.setParentCityId(3); return cityInfoDictMapper.insert(cityInfoDict); }
二、注解失效原因分析
其實這還是由於使用Spring AOP
代理造成的,因為只有當事務方法被當前類以外的代碼調用時,才會由Spring
生成的代理對象來管理。
三、解決方案
利用spring提供的動態代理機制。
1、引入spring動態代理:
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、方法調用改成動態代理調用:
@GetMapping("/test") private Integer A() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("2"); // B 插入字段為 3的數據
Test test = (Test)AopContext.currentProxy(); int insert=test.insertB(); int insert = cityInfoDictMapper.insert(cityInfoDict); return insert; } @Transactional() public Integer insertB() throws Exception { CityInfoDict cityInfoDict = new CityInfoDict(); cityInfoDict.setCityName("3"); cityInfoDict.setParentCityId(3); return cityInfoDictMapper.insert(cityInfoDict); }
可能會提示:Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
ze在代理類上添加:@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) 問題解決!!!!!!
以上代碼示例見:https://blog.csdn.net/zhangkaixuan456/article/details/109082645
二、Spring事務生效的情況、原理分析及如何解決
一、使用默認的事務處理方式
因為在java的設計中,它認為不繼承RuntimeException的異常是”checkException”或普通異常,如IOException,這些異常在java語法中是要求強制處理的。對於這些普通異常,spring默認它們都已經處理,所以默認不回滾。可以添加rollbackfor=Exception.class來表示所有的Exception都回滾。
二、內部調用
不帶事務的方法調用該類中帶事務的方法,不會回滾。因為spring的回滾是用代理模式生成的,如果是一個不帶事務的方法調用該類的帶事務的方法,直接通過this.xxx()
調用,而不生成代理事務,所以事務不起作用。常見解決方法是,拆類。
spring中在一個擁有事務的方法A中調用另一個會掛起事務並創建新事務的方法B,如果使用this調用這個方法B,此時方法B拋出了一個異常,此時的方法B的事務會失效的,並不會回滾。
PROPAGATION_REQUIRED:
如果存在一個事務,則支持當前事務。如果沒有事務則開啟事務;
PROPAGATION_REQUIRES_NEW:
總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。
@Service public class EmployeeService { @Autowired private EmployeeDao employeeDao; public void save(){ try { this.saveEmployee(); //此處this調用不會開啟事務,數據會被保存
}catch (Exception e){ e.printStackTrace(); } } @Transactional(propagation = Propagation.PROPAGATION_REQUIRED) //此處無論是PROPAGATION_REQUIRED還是PROPAGATION_REQUIRES_NEW,事務均不生效
public void saveEmployee(){ Employee employee = new Employee(); employee.setName("zhangsan"); employee.setAge("26"; employeeDao.save(employee); throw new RuntimeException(); } }
三、問題原因
原因在於JDK的動態代理,只有被動態代理直接調用時才會產生事務。在SpringIoC容器中返回的調用的對象是代理對象而不是真實的對象,而這里的this是EmployeeService
真實對象而不是代理對象。
四、解決辦法:
1、方法1、在方法A上開啟事務,方法B不用事務或默認事務,並在方法A的catch中throw new RuntimeException();
在沒指定rollbackFor時,默認回滾的異常為RuntimeException
,這樣使用的就是方法A的事務。(一定要throw new RuntimeException();
否則異常被捕捉處理,同樣不會回滾。)如下
@Transactional() //開啟事務
public void save(){ try { this.saveEmployee(); //這里this調用會使事務失效,數據會被保存
}catch (Exception e){ e.printStackTrace(); throw new RuntimeException(); } }
2、方法2:方法A上不開啟事務,方法B上開啟事務,並在方法A中將 this 調用改成動態代理調用AopContext.currentProxy(),如下:
public void save(){ try { EmployeeService proxy =(EmployeeService) AopContext.currentProxy(); proxy.saveEmployee(); }catch (Exception e){ e.printStackTrace(); } }
三、至於緩存或異常是同樣的問題原因