1. spring 事務(wù)失效 描述
工作中經(jīng)常使用spring聲明式事務(wù)鲁豪,使用起來很方便耘子,只需要在需要添加事務(wù)的方法上面添加一個@Transaction
注解即可,對開發(fā)人員來說完全是透明的實現(xiàn)。spring使用AOP來支持聲明式事務(wù),會根據(jù)事務(wù)屬性疮绷,自動在方法調(diào)用之前決定是否開啟一個事務(wù),并在方法執(zhí)行之后決定事務(wù)提交或回滾事務(wù)嚣潜。在使用過程中冬骚,遇到過一種情況:就是同一個類中無事務(wù)方法調(diào)用有事務(wù)方法會導(dǎo)致有事務(wù)方法不生效,起始不僅僅是spring事務(wù)這種情況會失效,Spring AOP 這種情況下也是不起作用的郑原,下面就來探索下這種情況的原因唉韭。
2 動態(tài)代理模擬spring 事務(wù)實現(xiàn)
spring 的聲明式事務(wù),其根本實現(xiàn)原理就是動態(tài)代理的方式,下面采用java JDK動態(tài)代理的方式來模擬spring 事務(wù)的實現(xiàn)犯犁,然后展示spring 事務(wù)失效的現(xiàn)象属愤。
2.1 定義接口
Tx 是 Transaction的縮寫。
public interface TxService {
/**
* 事務(wù)方法
*/
void txM();
/**
* 無事務(wù)方法
*/
void noTxM();
}
2.2 模擬事務(wù)失效
@Test
public void testTx(){
//被代理對象
TxServiceImpl target = new TxServiceImpl();
//代理對象
TxService proxyInstance =(TxService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new TxInvocationHandler(target));
System.out.println("測試1:執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法");
proxyInstance.noTxM();
System.out.println("-------------分割線------------");
System.out.println("測試2:執(zhí)行有事務(wù)方法");
proxyInstance.txM();
}
/**
* 攔截 目標(biāo)方法酸役,進(jìn)行事務(wù)管理
*/
class TxInvocationHandler implements InvocationHandler{
/**
* 被代理類實列
*/
private Object target;
public TxInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判斷是否是事務(wù)方法
boolean isTxMethod = isTxMethod(method,target);
//獲取數(shù)據(jù)庫連接
System.out.println("獲取數(shù)據(jù)庫連接");
//事務(wù)開啟
startTx(isTxMethod);
Object result ;
try {
//執(zhí)行被代理類對象方法
result = method.invoke(target, args);
//事務(wù)提交
commitTx(isTxMethod);
return result;
}catch (Exception e){
//事務(wù)回滾
rollBack(isTxMethod);
throw new Exception(e);
}finally {
//關(guān)閉數(shù)據(jù)庫連接
closeConnection();
}
}
/**
* 關(guān)閉數(shù)據(jù)庫連接
*/
private void closeConnection() {
System.out.println("關(guān)閉數(shù)據(jù)庫連接");
}
/**
* 判斷是否是 一個事務(wù)方法
*/
private boolean isTxMethod(Method method,Object target) {
try {
Transactional annotation = AnnotationUtils.findAnnotation(method, Transactional.class);
if(annotation == null){
Method tagetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
annotation = AnnotationUtils.findAnnotation(tagetMethod, Transactional.class);
}
return annotation != null;
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return false;
}
/**
* 提交事務(wù)
*/
private void commitTx(boolean isTxMethod) {
if(isTxMethod){
System.out.println(":事務(wù)提交");
}
}
/**
* 回滾事務(wù)
*/
private void rollBack(boolean isTxMethod) {
if(isTxMethod){
System.out.println(":事務(wù)回滾");
}
}
/**
* 根據(jù)條件事務(wù)開啟
*/
private void startTx(boolean isTxMethod) {
if(isTxMethod){
System.out.println(":開啟事務(wù)");
}
}
}
2.3 執(zhí)行testTx()結(jié)果
從結(jié)果可以看出測試1 在執(zhí)行有事務(wù)方法是并沒有開啟事務(wù)住诸。
***測試1:執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法:
獲取數(shù)據(jù)庫連接
執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->(
執(zhí)行事務(wù)方法
)
關(guān)閉數(shù)據(jù)庫連接
----------------------分割線-----------------------
***測試2:執(zhí)行有事務(wù)方法:
獲取數(shù)據(jù)庫連接
:開啟事務(wù)
執(zhí)行事務(wù)方法
:事務(wù)提交
關(guān)閉數(shù)據(jù)庫連接
3 查看動態(tài)代理字節(jié)碼文件
按照保存java 動態(tài)代理生成的字節(jié)碼文件介紹的方法獲取到動態(tài)代理生成的字節(jié)碼文件驾胆。
3.1 動態(tài)代理類中noTxM方法的實現(xiàn)
1.生成的動態(tài)代理類繼承了 Proxy
類 并且實現(xiàn)了TxService
接口
2.Proxy
的h屬性 就是 InvacationHandler
實現(xiàn)類,既是我們的TxInvocationHandler
3.接口方法的實現(xiàn)都是 調(diào)用了 InvocationHandler
的invoke方法。invoke方法的實現(xiàn)就是實現(xiàn)事務(wù)管理的地方贱呐。
public final class $Proxy extends Proxy implements TxService {
//省略 屬性 其他方法
public final void noTxM() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
3.2 代理類UML圖
3.3 繪制調(diào)用時序圖
通過 字節(jié)碼文件可以繪制一下調(diào)用時序圖(無事務(wù)方法調(diào)用有事務(wù)方法):
4 事務(wù)失效原因分析
- 項目中丧诺,通過spring的 自動注入 獲取到 TxService類型的對象,其實是一個動態(tài)代理類增強(qiáng)對象$Proxy奄薇。
- 在執(zhí)行TxService.noTxM()方法時驳阎,實際執(zhí)行的$Proxy.noTxM(),接著會執(zhí)行InvocationHandler.invoke 方法。
- 在invoke方法內(nèi)判斷noTxM()并不是一個事務(wù)方法馁蒂,所以事務(wù)并沒有開啟呵晚。
- 在事務(wù)沒有開啟的情況下,執(zhí)行被代理類對象target .noTxM() ,在noTxM()內(nèi)執(zhí)行txM()函數(shù) ,InvocationHandler.invoke()并不會再次執(zhí)行,所以也就不會為txM()開啟事務(wù)了沫屡。
- 如果noTxM()調(diào)用txM()時饵隙,能使用代理類對象調(diào)用,事務(wù)就不會失效。
- 這種情況不僅僅存在于事務(wù)管理中沮脖,同樣Spring AOP 也會有這種情況金矛,因為事務(wù)是基于AOP 實現(xiàn)的。
5 解決方案
-
通過
@Resouse
或@Autowired
注入自己:這樣獲取到的就是代理類對象勺届。@Service public class TxServiceImpl implements TxService { @Autowired private TxService txService; @Transactional @Override public void txM() { System.out.println("執(zhí)行事務(wù)方法"); } @Override public void noTxM() { System.out.println("執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->("); txService.txM(); System.out.println(")"); } }
-
AopContext.currentProxy()方式
spring boot 項目 需要在啟動類添加@EnableAspectJAutoProxy(exposeProxy = true)
驶俊,spring 項目需要配置<aop:aspectj-autoproxy proxy-target-class="true"expose-proxy="true"/> ,否則會拋異常免姿。@Override public void noTxM() { System.out.println("執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->("); TxService txService = (TxService) AopContext.currentProxy(); txService.txM(); System.out.println(")"); }
從Spring IOC 容器中獲取到代理類對象
@Override
public void noTxM() {
System.out.println("執(zhí)行無事務(wù)方法調(diào)用有事務(wù)方法->(");
TxService txService = SpringContextUtil.getBean(TxService.class);
txService.txM();
System.out.println(")");
}