前言
最近在開發業務代碼的時候,犯了一個事務注解的錯誤:在同一個類的非事務方法中調用了另一個事務方法,導緻事務沒有生效,如下所示:
public ConfirmOrderResultVO batchConfirmPurchaseOrders(Long taobaoUserId, List<String> bizOrderIds) throws TCException {
………………………………………………………………
for (String bizOrderId : bizOrderIds) {
// 推單成功進入successList,否則進入failedList
if (confirmPurchaseOrder(taobaoUserId, bizOrderId)){
successList.add(Long.valueOf(bizOrderId));
}else {
failedList.add(Long.valueOf(bizOrderId));
}
}
………………………………………………………………
}
其中confirmPurchaseOrder()是一個事務方法:
@Transactional
public Boolean confirmPurchaseOrder(Long taobaoUserId, String bizOrderId) throws TCException {
logger.warn("|ConfirmPurchaseOrder|UserId:" taobaoUserId ",orderId:" bizOrderId);
…………………………
return ture;
}
這樣在直接調用batchConfirmPurchaseOrders()方法時,如果confirmPurchaseOrder()方法發生了異常,是不會回滾的。原理在于Spring的事務管理依靠的動态代理模式,當在同一個類中調用一個非事務方法,是不會生成代理對象的,自然也不會觸發事務。借此機會回顧一下代理模式和Spring事務管理的原理。
代理模式
網上講解代理模式的文章千奇百怪,很多時候看完了也不明白講的重點是什麼。
事實上在生活中我們常常會遇到各種各樣的代理模式,例如火車票代售點代理出售火車票,他在“幫忙”出售火車票的同時,收取了額外的手續費,記錄了自己店裡的流水等。
又比如班長代理老師來上交班費,他在“上交班費”的動作之前,還進行了檢查班級同學是否到齊、向每一位同學收班費、核對班費金額,最後再上交班費。
總而言之,代理模式就是 代理其他對象,在完成原動作的基礎上(前後)做一些額外的自定義的工作 。
聰明的朋友看到這裡一定想到了:那我在一個方法中調用另一個方法不就是代理模式了嗎?啊對對對,從功能上來講是一樣的,但是“模式”之所以為“模式”,以我拙見,其本質目的在于形成規範,以減少重複代碼的編寫。因此如果不能達到減少代碼重複的本質目的,便不能稱之為“模式”。
按照代理模式的實現方式,分為兩種:靜态代理和動态代理。那我們就拿剛剛說過的“寫作業”這件事來做例子,講解一下兩種實現方式的區别。
▐靜态代理首先定義一個“作業”接口,裡面有一個方法“做作業”
public interface Homework {
void doHomework();
}
小紅實現了這個接口。但是小紅是一個不愛學習的小學生,又因為師從馬掌門成為了學校的扛把子,所以為了不讓老師發現,他決定自己随便做做,把剩下的部分交給小弟們來做。
public class XiaoHong implements Homework{
@Override
public void doHomework() {
System.out.println("XiaoHong did homework casually");
}
}
其中小王、小張兩個小弟成績優異,一個負責數學作業,一個負責英語作業,于是他們兩個自告奮勇實現了Homework接口,在代理完成作業的基礎上,還不斷學習提高自己的能力,并且對作業答案進行了校驗。
小王:
public class XiaoWang implements Homework{
// 持有Homework屬性
private Homework homework;
// 通過構造函數初始化Homework
public XiaoWang(Homework homework){
this.homework=homework;
}
//代理實現
@Override
public void doHomework() {
doStudy();
homework.doHomework();
System.out.println("XiaoWang helps with MathHomework");
doCheck();
}
// 額外方法
private void doCheck() {
System.out.println("XiaoWang is checking-----------------");
}
// 額外方法
private void doStudy() {
System.out.println("XiaoWang is studying---------------");
}
}
小張:
public class XiaoZhang implements Homework{
// 持有Homework屬性
private Homework homework;
// 通過構造函數初始化Homework
public XiaoZhang(Homework homework){
this.homework=homework;
}
//代理實現
@Override
public void doHomework() {
doStudy();
homework.doHomework();
System.out.println("XiaoZhang helps with EngHomework");
doCheck();
}
// 額外方法
private void doCheck() {
System.out.println("XiaoZhang is checking-----------------");
}
// 額外方法
private void doStudy() {
System.out.println("XiaoZhang is studying---------------");
}
}
于是,小紅可以放心地把作業交給小王和小張了:
public static void main(String[] args) {
// 實例化一個目标對象
XiaoHong xiaoHong = new XiaoHong();
// 把目标對象通過構造函數傳遞給代理對象
XiaoZhang xiaoZhang =new XiaoZhang(xiaoHong);
XiaoWang xiaoWang =new XiaoWang(xiaoHong);
// 調用代理對象的方法
xiaoZhang.doHomework();
xiaoWang.doHomework();
}
輸出:
XiaoZhang is studying---------------
XiaoHong did homework casually
XiaoZhang helps with EngHomework
XiaoZhang is checking-----------------
XiaoWang is studying---------------
XiaoHong did homework casually
XiaoWang helps with MathHomework
XiaoWang is checking-----------------
問題來了,如果老師又布置了一個Book接口和readBook方法,但是readBook前後的動作是一樣的(都需要study和check),如何通過代理來實現呢?
一個方案是讓小王和小張都實現Book接口和readBook方法,并持有新的Book對象;另一個方案是再找一個小趙實現Book接口和readBook方法。
這樣兩種方案實際上都能達到效果,但是如果接口和方法增多起來呢?如果讓一個代理類實現一堆方法,并持有一堆不同的對象,這個類勢必會變得臃腫不堪;如果每一個新的方法都創建一個新的代碼類,引用不同的對象,又會造成代碼的大量重複。因此,動态代理就出現了。
▐動态代理前文所講的靜态代理之所以為“靜态”,是因為每個接口的每個方法,我們都要顯式地去實現、創建、調用,所有的操作都是寫死的、是靜态的。而動态代理的巧妙之處在于,可以在程序的運行期間,動态的生成不同的代理類,來完成相同的工作。也就是說,如果你想實現一個方法,在所有其他方法執行前後做一些額外的相同的動作,例如打印日志、記錄方法執行時間等,你就需要動态代理了(是不是想到了什麼?)。
事實上所有的動态代理思想都是一緻的,而目前最常用的動态代理實現有兩種:JDK原生實現和Cglib開源實現。
Spring在5.X之前默認的動态代理實現一直是JDK動态代理。但是從5.X開始,Spring就開始默認使用Cglib來作為動态代理實現。并且SpringBoot從2.X開始也轉向了Cglib動态代理實現。
現在有一個牛X的機器人代理,可以幫所有人在完成老師布置的任何任務前後,進行學習和答案的核對,在JDK的動态代理實現中,他是這樣編寫的:
// 實現InvocationHandler
public class RobotProxy implements InvocationHandler {
// 持有一個Object類型的目标對象target
private Object target;
// 通過構造函數實例化目标對象target
public RobotProxy(Object target) {
this.target = target;
}
// 重寫invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doStudy();
// 執行目标對象的方法
Object invoke = method.invoke(target, args);
doCheck();
return invoke;
}
// 額外動作
private void doStudy() {
System.out.println("Robot is studying------------");
}
// 額外動作
private void doCheck() {
System.out.println("Robot is checking------------");
}
}
然後這樣調用機器人代理:
public static void main(String[] args) {
// 實例化一個目标對象
XiaoHong xiaoHong = new XiaoHong();
// 傳入實現的接口 new Class[]{Homework.class}
// 以及代理類 new RobotProxy(xiaoHong)
Homework homework = (Homework)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Homework.class}, new RobotProxy(xiaoHong));
homework.doHomework();
}
輸出:
Robot is studying------------
XiaoHong did homework casually
Robot is checking------------
可以看到,JDK實現動态代理必須要依賴接口,隻有實現了接口的目标類才可以被代理,而Cglib動态代理就可以消除接口的限制。
創建一個升級版的機器人RovotV2代理類:
// 實現MethodInterceptor方法(要引入cglib包)
public class RobotV2Proxy implements MethodInterceptor {
// 重寫intercept方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
doStudy();
Object invoke = methodProxy.invokeSuper(o, objects);
doCheck();
return invoke;
}
// 額外動作
private void doStudy() {
System.out.println("RobotV2 is studying------------");
}
// 額外動作
private void doCheck() {
System.out.println("RobotV2 is checking------------");
}
}
調用方式:
public static void main(String[] args) {
// 創建增強類
Enhancer enhancer = new Enhancer();
// 設置父類
enhancer.setSuperclass(XiaoHong.class);
// 設置回調
enhancer.setCallback(new RobotV2Proxy());
// 創建目标對象
XiaoHong xiaoHong = (XiaoHong)enhancer.create();
// 調用目标方法
xiaoHong.doHomework();
}
輸出:
RobotV2 is studying------------
XiaoHong did homework casually
RobotV2 is checking------------
Cglib動态代理相比于JDK代理實現有幾個優勢:
可以看到,動态代理隻需要一個代理類,就可以代理所有需要同一種處理(如上文所述的study()、check())的類和方法,這就是動态代理與靜态代理最大的區别。
Spring事務管理
OK,了解到了代理模式的好處之後,就可以自然而然的想到Spring事務管理的基本原理了。無非是借助動态代理,做一些額外的操作:在真正的方法執行之前開啟事務,在方法順利執行完成之後提交事務,如果方法執行過程中發生異常,要執行回滾操作。
一般有兩種方法使用Spring的事務管理,一種是利用注解,一種是手動編程。本文因為使用的是注解型的事務管理,因此隻對注解型事務的使用和原理進行探究,以找到事務沒有生效的原因。
▐注解型事務注解型事務的使用非常簡單,隻需要在需要事務管理的類或方法上加上@Transactional(rollbackFor = Exception.class),其中rollbackFor指定的是遇到什麼情況下執行回滾。
當然這個注解還有傳播級别、隔離級别等我們背過的八股文屬性,就不一一展開了。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!