【轉載】@Transactional 注解的12種失效場景

原文啪!啪居兆!@Transactional 注解的12種失效場景覆山,這坑我踩個遍 (qq.com)
今天我們就一起聊聊,事務失效的一些場景史辙,說不定你已經中招了汹买。不信佩伤,讓我們一起看看。

圖片

一 事務不生效

1.訪問權限問題

眾所周知晦毙,java的訪問權限主要有四種:private生巡、default、protected见妒、public孤荣,它們的權限從左到右,依次變大须揣。

但如果我們在開發(fā)過程中盐股,把有某些事務方法,定義了錯誤的訪問權限耻卡,就會導致事務功能出問題疯汁,例如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Service
public class UserService {

@Transactional
private void add(UserModel userModel) {
     saveData(userModel);
     updateData(userModel);
}

}` </pre>

我們可以看到add方法的訪問權限被定義成了private,這樣會導致事務失效卵酪,spring要求被代理方法必須是public的幌蚊。

說白了,在AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個判斷溃卡,如果目標方法不是public溢豆,則TransactionAttribute返回null,即不支持事務瘸羡。

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}

// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
  return txAttr;
}

// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
  return txAttr;
}

if (specificMethod != method) {
  // Fallback is to look at the original method.
  txAttr = findTransactionAttribute(method);
  if (txAttr != null) {
    return txAttr;
  }
  // Last fallback is the class of the original method.
  txAttr = findTransactionAttribute(method.getDeclaringClass());
  if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
    return txAttr;
  }
}
return null;

}` </pre>

也就是說漩仙,如果我們自定義的事務方法(即目標方法),它的訪問權限不是public犹赖,而是private队他、default或protected的話,spring則不會提供事務功能冷尉。

2. 方法用final修飾

有時候漱挎,某個方法不想被子類重新,這時可以將該方法定義成final的雀哨。普通方法這樣定義是沒問題的,但如果將事務方法定義成final私爷,例如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Service
public class UserService {

@Transactional
public final void add(UserModel userModel){
    saveData(userModel);
    updateData(userModel);
}

}` </pre>

我們可以看到add方法被定義成了final的雾棺,這樣會導致事務失效。

為什么衬浑?

如果你看過spring事務的源碼捌浩,可能會知道spring事務底層使用了aop,也就是通過jdk動態(tài)代理或者cglib工秩,幫我們生成了代理類尸饺,在代理類中實現的事務功能进统。

但如果某個方法用final修飾了,那么在它的代理類中浪听,就無法重寫該方法螟碎,而添加事務功能。

注意:如果某個方法是static的迹栓,同樣無法通過動態(tài)代理掉分,變成事務方法。

3.方法內部調用

有時候我們需要在某個Service類的某個方法中克伊,調用另外一個事務方法酥郭,比如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@Transactional
public void add(UserModel userModel) {
    userMapper.insertUser(userModel);
    updateStatus(userModel);
}

@Transactional
public void updateStatus(UserModel userModel) {
    doSameThing();
}

}` </pre>

我們看到在事務方法add中,直接調用事務方法updateStatus愿吹。從前面介紹的內容可以知道不从,updateStatus方法擁有事務的能力是因為spring aop生成代理了對象,但是這種方法直接調用了this對象的方法犁跪,所以updateStatus方法不會生成事務椿息。

由此可見,在同一個類中的方法直接內部調用耘拇,會導致事務失效撵颊。

那么問題來了,如果有些場景惫叛,確實想在同一個類的某個方法中倡勇,調用它自己的另外一個方法,該怎么辦呢嘉涌?

3.1 新加一個Service方法

這個方法非常簡單妻熊,只需要新加一個Service方法,把@Transactional注解加到新Service方法上仑最,把需要事務執(zhí)行的代碼移到新方法中扔役。具體代碼如下:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;

public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}

@Servcie
public class ServiceB {

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
   addData1();
   updateData2();
}

}` </pre>

3.2 在該Service類中注入自己

如果不想再新加一個Service類,在該Service類中注入自己也是一種選擇警医。具體代碼如下:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;

public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}` </pre>

可能有些人可能會有這樣的疑問:這種做法會不會出現循環(huán)依賴問題亿胸?

答案:不會。

其實spring ioc內部的三級緩存保證了它预皇,不會出現循環(huán)依賴問題侈玄。但有些坑,如果你想進一步了解循環(huán)依賴問題吟温,可以看看我之前文章《spring:我是如何解決循環(huán)依賴的序仙?》。

3.3 通過AopContent類

在該Service類中使用AopContext.currentProxy()獲取代理對象

上面的方法2確實可以解決問題鲁豪,但是代碼看起來并不直觀潘悼,還可以通過在該Service類中使用AOPProxy獲取代理對象律秃,實現相同的功能。具體代碼如下:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Servcie
public class ServiceA {

public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}` </pre>

4.未被spring管理

在我們平時開發(fā)過程中治唤,有個細節(jié)很容易被忽略棒动。即使用spring事務的前提是:對象要被spring管理,需要創(chuàng)建bean實例肝劲。

通常情況下迁客,我們通過@Controller、@Service辞槐、@Component掷漱、@Repository等注解,可以自動實現bean實例化和依賴注入的功能榄檬。

當然創(chuàng)建bean實例的方法還有很多卜范,有興趣的小伙伴可以看看我之前寫的另一篇文章《@Autowired的這些騷操作,你都知道嗎鹿榜?

如果有一天海雪,你匆匆忙忙的開發(fā)了一個Service類,但忘了加@Service注解舱殿,比如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`//@Service
public class UserService {

@Transactional
public void add(UserModel userModel) {
     saveData(userModel);
     updateData(userModel);
}    

}` </pre>

從上面的例子奥裸,我們可以看到UserService類沒有加@Service注解,那么該類不會交給spring管理沪袭,所以它的add方法也不會生成事務湾宙。

5.多線程調用

在實際項目開發(fā)中,多線程的使用場景還是挺多的冈绊。如果spring事務用在多線程場景中侠鳄,會有問題嗎?

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {
    userMapper.insertUser(userModel);
    new Thread(() -> {
        roleService.doOtherThing();
    }).start();
}

}

@Service
public class RoleService {

@Transactional
public void doOtherThing() {
    System.out.println("保存role表數據");
}

}` </pre>

從上面的例子中死宣,我們可以看到事務方法add中伟恶,調用了事務方法doOtherThing,但是事務方法doOtherThing是在另外一個線程中調用的毅该。

這樣會導致兩個方法不在同一個線程中博秫,獲取到的數據庫連接不一樣,從而是兩個不同的事務眶掌。如果想doOtherThing方法中拋了異常台盯,add方法也回滾是不可能的。

如果看過spring事務源碼的朋友畏线,可能會知道spring的事務是通過數據庫連接來實現的。當前線程中保存了一個map良价,key是數據源寝殴,value是數據庫連接蒿叠。

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`private static final ThreadLocal<Map<Object, Object>> resources =

new NamedThreadLocal<>("Transactional resources");` </pre>

我們說的同一個事務,其實是指同一個數據庫連接蚣常,只有擁有同一個數據庫連接才能同時提交和回滾市咽。如果在不同的線程,拿到的數據庫連接肯定是不一樣的抵蚊,所以是不同的事務施绎。

6.表不支持事務

周所周知,在mysql5之前贞绳,默認的數據庫引擎是myisam谷醉。

它的好處就不用多說了:索引文件和數據文件是分開存儲的,對于查多寫少的單表操作冈闭,性能比innodb更好俱尼。

有些老項目中,可能還在用它萎攒。

在創(chuàng)建表的時候遇八,只需要把ENGINE參數設置成MyISAM即可:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">CREATE TABLEcategory(idbigint NOT NULL AUTO_INCREMENT,one_categoryvarchar(20) COLLATE utf8mb4_bin DEFAULT NULL,two_categoryvarchar(20) COLLATE utf8mb4_bin DEFAULT NULL,three_categoryvarchar(20) COLLATE utf8mb4_bin DEFAULT NULL,four_categoryvarchar(20) COLLATE utf8mb4_bin DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin </pre>

myisam好用,但有個很致命的問題是:不支持事務耍休。

如果只是單表操作還好刃永,不會出現太大的問題。但如果需要跨多張表操作羊精,由于其不支持事務斯够,數據極有可能會出現不完整的情況。

此外园匹,myisam還不支持行鎖和外鍵雳刺。

所以在實際業(yè)務場景中,myisam使用的并不多裸违。在mysql5以后掖桦,myisam已經逐漸退出了歷史的舞臺,取而代之的是innodb供汛。

有時候我們在開發(fā)的過程中枪汪,發(fā)現某張表的事務一直都沒有生效,那不一定是spring事務的鍋怔昨,最好確認一下你使用的那張表雀久,是否支持事務。

7.未開啟事務

有時候趁舀,事務沒有生效的根本原因是沒有開啟事務赖捌。

你看到這句話可能會覺得好笑。

開啟事務不是一個項目中矮烹,最最最基本的功能嗎越庇?

為什么還會沒有開啟事務罩锐?

沒錯,如果項目已經搭建好了卤唉,事務功能肯定是有的涩惑。

但如果你是在搭建項目demo的時候,只有一張表桑驱,而這張表的事務沒有生效竭恬。那么會是什么原因造成的呢?

當然原因有很多熬的,但沒有開啟事務痊硕,這個原因極其容易被忽略。

如果你使用的是springboot項目悦析,那么你很幸運寿桨。因為springboot通過DataSourceTransactionManagerAutoConfiguration類,已經默默的幫你開啟了事務强戴。

你所要做的事情很簡單亭螟,只需要配置spring.datasource相關參數即可。

但如果你使用的還是傳統(tǒng)的spring項目骑歹,則需要在applicationContext.xml文件中预烙,手動配置事務相關參數。如果忘了配置道媚,事務肯定是不會生效的扁掸。

具體配置如下信息:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> </aop:config> </pre>

默默的說一句,如果在pointcut標簽中的切入點匹配規(guī)則最域,配錯了的話谴分,有些類的事務也不會生效。

二 事務不回滾

1.錯誤的傳播特性

其實镀脂,我們在使用@Transactional注解時牺蹄,是可以指定propagation參數的。

該參數的作用是指定事務的傳播特性薄翅,spring目前支持7種傳播特性:

  • REQUIRED 如果當前上下文中存在事務沙兰,那么加入該事務,如果不存在事務翘魄,創(chuàng)建一個事務鼎天,這是默認的傳播屬性值。
  • SUPPORTS 如果當前上下文存在事務暑竟,則支持事務加入事務斋射,如果不存在事務,則使用非事務的方式執(zhí)行。
  • MANDATORY 如果當前上下文中存在事務绩鸣,否則拋出異常怀大。
  • REQUIRES_NEW 每次都會新建一個事務,并且同時將上下文中的事務掛起呀闻,執(zhí)行當前新建事務完成以后,上下文事務恢復再執(zhí)行潜慎。
  • NOT_SUPPORTED 如果當前上下文中存在事務捡多,則掛起當前事務,然后新的方法在沒有事務的環(huán)境中執(zhí)行铐炫。
  • NEVER 如果當前上下文中存在事務垒手,則拋出異常,否則在無事務環(huán)境上執(zhí)行代碼倒信。
  • NESTED 如果當前上下文中存在事務科贬,則嵌套事務執(zhí)行,如果不存在事務鳖悠,則新建事務榜掌。

如果我們在手動設置propagation參數的時候陕靠,把傳播特性設置錯了狼犯,比如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Service
public class UserService {

@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
    saveData(userModel);
    updateData(userModel);
}

}` </pre>

我們可以看到add方法的事務傳播特性定義成了Propagation.NEVER钉赁,這種類型的傳播特性不支持事務潦闲,如果有事務則會拋異常锻梳。

目前只有這三種傳播特性才會創(chuàng)建新事務:REQUIRED田柔,REQUIRES_NEW舌界,NESTED综芥。

2.自己吞了異常

事務不會回滾九妈,最常見的問題是:開發(fā)者在代碼中手動try...catch了異常反砌。比如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Slf4j
@Service
public class UserService {

@Transactional
public void add(UserModel userModel) {
    try {
        saveData(userModel);
        updateData(userModel);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
    }
}

}` </pre>

這種情況下spring事務當然不會回滾,因為開發(fā)者自己捕獲了異常萌朱,又沒有手動拋出宴树,換句話說就是把異常吞掉了。

如果想要spring事務能夠正橙峦茫回滾森渐,必須拋出它能夠處理的異常。如果沒有拋異常冒晰,則spring認為程序是正常的同衣。

3.手動拋了別的異常

即使開發(fā)者沒有手動捕獲異常,但如果拋的異常不正確壶运,spring事務也不會回滾耐齐。

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Slf4j
@Service
public class UserService {

@Transactional
public void add(UserModel userModel) throws Exception {
    try {
         saveData(userModel);
         updateData(userModel);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new Exception(e);
    }
}

}` </pre>

上面的這種情況,開發(fā)人員自己捕獲了異常,又手動拋出了異常:Exception埠况,事務同樣不會回滾耸携。

因為spring事務,默認情況下只會回滾RuntimeException(運行時異常)和Error(錯誤)辕翰,對于普通的Exception(非運行時異常)夺衍,它不會回滾。

4.自定義了回滾異常

在使用@Transactional注解聲明事務時喜命,有時我們想自定義回滾的異常沟沙,spring也是支持的”陂牛可以通過設置rollbackFor參數矛紫,來完成這個功能。

但如果這個參數的值設置錯了牌里,就會引出一些莫名其妙的問題颊咬,例如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Slf4j
@Service
public class UserService {

@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
   saveData(userModel);
   updateData(userModel);
}

}` </pre>

如果在執(zhí)行上面這段代碼,保存和更新數據時牡辽,程序報錯了喳篇,拋了SqlException、DuplicateKeyException等異常催享。而BusinessException是我們自定義的異常杭隙,報錯的異常不屬于BusinessException,所以事務也不會回滾因妙。

即使rollbackFor有默認值痰憎,但阿里巴巴開發(fā)者規(guī)范中,還是要求開發(fā)者重新指定該參數攀涵。

這是為什么呢铣耘?

因為如果使用默認值,一旦程序拋出了Exception以故,事務不會回滾蜗细,這會出現很大的bug。所以怒详,建議一般情況下炉媒,將該參數設置成:Exception或Throwable。

5.嵌套事務回滾多了

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`public class UserService {

@Autowired
private UserMapper userMapper;

@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {
    userMapper.insertUser(userModel);
    roleService.doOtherThing();
}

}

@Service
public class RoleService {

@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
    System.out.println("保存role表數據");
}

}` </pre>

這種情況使用了嵌套的內部事務昆烁,原本是希望調用roleService.doOtherThing方法時吊骤,如果出現了異常,只回滾doOtherThing方法里的內容静尼,不回滾 userMapper.insertUser里的內容白粉,即回滾保存點传泊。。但事實是鸭巴,insertUser也回滾了眷细。

why?

因為doOtherThing方法出現了異常,沒有手動捕獲鹃祖,會繼續(xù)往上拋溪椎,到外層add方法的代理方法中捕獲了異常。所以惯豆,這種情況是直接回滾了整個事務池磁,不只回滾單個保存點。

怎么樣才能只回滾保存點呢楷兽?

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {

    userMapper.insertUser(userModel);
    try {
        roleService.doOtherThing();
    } catch (Exception e) {
        log.error(e.getMessage(), e);
    }
}

}` </pre>

可以將內部嵌套事務放在try/catch中,并且不繼續(xù)往上拋異常华临。這樣就能保證芯杀,如果內部嵌套事務中出現異常,只回滾內部事務雅潭,而不影響外部事務揭厚。

三 其他

1 大事務問題

在使用spring事務時,有個讓人非常頭疼的問題扶供,就是大事務問題筛圆。

通常情況下,我們會在方法上@Transactional注解椿浓,填加事務功能太援,比如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`@Service
public class UserService {

@Autowired 
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {
   query1();
   query2();
   query3();
   roleService.save(userModel);
   update(userModel);
}

}

@Service
public class RoleService {

@Autowired 
private RoleService roleService;

@Transactional
public void save(UserModel userModel) throws Exception {
   query4();
   query5();
   query6();
   saveData(userModel);
}

}` </pre>

@Transactional注解,如果被加到方法上扳碍,有個缺點就是整個方法都包含在事務當中了提岔。

上面的這個例子中,在UserService類中笋敞,其實只有這兩行才需要事務:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">roleService.save(userModel); update(userModel); </pre>

在RoleService類中碱蒙,只有這一行需要事務:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">saveData(userModel); </pre>

現在的這種寫法,會導致所有的query方法也被包含在同一個事務當中夯巷。

如果query方法非常多赛惩,調用層級很深,而且有部分查詢方法比較耗時的話趁餐,會造成整個事務非常耗時,而從造成大事務問題阶牍。

關于大事務問題的危害琳状,可以閱讀一下我的另一篇文章《讓人頭痛的大事務問題到底要如何解決?》,上面有詳細的講解。

圖片

2.編程式事務

上面聊的這些內容都是基于@Transactional注解的趣倾,主要說的是它的事務問題,我們把這種事務叫做:聲明式事務

其實驹止,spring還提供了另外一種創(chuàng)建事務的方式,即通過手動編寫代碼實現的事務砖第,我們把這種事務叫做:編程式事務羽杰。例如:

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; color: rgb(63, 63, 63); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"> `@Autowired
private TransactionTemplate transactionTemplate;

...

public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}` </pre>

在spring中為了支持編程式事務,專門提供了一個類:TransactionTemplate腌零,在它的execute方法中闲询,就實現了事務的功能记舆。

相較于@Transactional注解聲明式事務厚满,我更建議大家使用,基于TransactionTemplate的編程式事務丰榴。主要原因如下:

  1. 避免由于spring aop問題货邓,導致事務失效的問題。
  2. 能夠更小粒度的控制事務的范圍多艇,更直觀逻恐。

建議在項目中少使用@Transactional注解開啟事務。但并不是說一定不能用它峻黍,如果項目中有些業(yè)務邏輯比較簡單复隆,而且不經常變動,使用@Transactional注解開啟事務開啟事務也無妨姆涩,因為它更簡單挽拂,開發(fā)效率更高,但是千萬要小心事務失效的問題骨饿。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末亏栈,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子宏赘,更是在濱河造成了極大的恐慌绒北,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件察署,死亡現場離奇詭異闷游,居然都是意外死亡,警方通過查閱死者的電腦和手機贴汪,發(fā)現死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門脐往,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扳埂,你說我怎么就攤上這事业簿。” “怎么了阳懂?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵梅尤,是天一觀的道長。 經常有香客問我希太,道長克饶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任誊辉,我火速辦了婚禮矾湃,結果婚禮上,老公的妹妹穿的比我還像新娘堕澄。我一直安慰自己邀跃,他們只是感情好霉咨,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拍屑,像睡著了一般途戒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上僵驰,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天喷斋,我揣著相機與錄音,去河邊找鬼蒜茴。 笑死星爪,一個胖子當著我的面吹牛,可吹牛的內容都是我干的粉私。 我是一名探鬼主播顽腾,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诺核!你這毒婦竟也來了抄肖?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤窖杀,失蹤者是張志新(化名)和其女友劉穎漓摩,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體入客,經...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡幌甘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了痊项。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡酥诽,死狀恐怖鞍泉,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情肮帐,我是刑警寧澤咖驮,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站训枢,受9級特大地震影響托修,放射性物質發(fā)生泄漏。R本人自食惡果不足惜恒界,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一睦刃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧十酣,春花似錦涩拙、人聲如沸际长。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽工育。三九已至,卻和暖如春搓彻,著一層夾襖步出監(jiān)牢的瞬間如绸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工旭贬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怔接,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓骑篙,卻偏偏與公主長得像蜕提,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子靶端,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容