淺析Spring事務失效的情況、原因分析及如何解決:快取、事務、非同步不能在同一個類中相互呼叫,會失效
專案中遇到的問題的結論,具體現象就不描述了,通過查資料,就是知道一點就行:快取、事務、非同步不能同一個類中相互呼叫,會失效(只能在別的類裡呼叫)。具體問題描述我們直接看網上的文章吧。
一、同一個類中方法呼叫,導致@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();
}
}
三、至於快取或異常是同樣的問題原因