<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring事務(wù)的這些坑,大家必須了解一下

          共 9829字,需瀏覽 20分鐘

           ·

          2022-10-21 08:13

          5d3b5e2c45a91a649d3eca0c0053de98.webp

          對(duì)于從事java開發(fā)工作的同學(xué)來(lái)說(shuō),spring的事務(wù)肯定再熟悉不過(guò)了。在某些業(yè)務(wù)場(chǎng)景下,如果同時(shí)有多張表的寫入操作,為了保證操作的原子性(要么同時(shí)成功,要么同時(shí)失敗)避免數(shù)據(jù)不一致的情況,我們一般都會(huì)使用spring事務(wù)。


          沒錯(cuò),spring事務(wù)大多數(shù)情況下,可以滿足我們的業(yè)務(wù)需求。但是今天我要告訴大家的是,它有很多坑,稍不注意事務(wù)就會(huì)失效。


          不信,我們一起看看。

          1.錯(cuò)誤的訪問權(quán)限





                @Service
          public?class?UserService?{

          ????@Autowired
          ????private?UserMapper userMapper;
          ????
          ????@Transactional
          ????private?void?add(UserModel userModel)?{
          ????????userMapper.insertUser(userModel);
          ????}
          }

          我們可以看到add方法的訪問權(quán)限被定義成了private,這樣會(huì)導(dǎo)致事務(wù)失效,spring要求被代理方法必須是public的。

          AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個(gè)判斷,如果目標(biāo)方法不是public,則TransactionAttribute返回null,即不支持事務(wù)。

              
                  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;
          ??}



          2.方法被定義成final的





                @Service
          public?class?UserService?{

          ????@Autowired
          ????private?UserMapper userMapper;

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

          我們可以看到add方法被定義成了final的,這樣會(huì)導(dǎo)致spring aop生成的代理對(duì)象不能復(fù)寫該方法,而讓事務(wù)失效。


          3.方法內(nèi)部調(diào)用





                @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();
          ????}
          }

          我們看到在事務(wù)方法add中,直接調(diào)用事務(wù)方法updateStatus。 從前面介紹的內(nèi)容可以知道,updateStatus方法擁有事務(wù)的能力是因?yàn)閟pring aop生成代理了對(duì)象,但是這種方法直接調(diào)用了this對(duì)象的方法,所以u(píng)pdateStatus方法不會(huì)生成事務(wù)。


          4.當(dāng)前實(shí)體沒有被spring管理





              
                  //@Service
          public?class?UserService?{

          ????@Autowired
          ????private?UserMapper userMapper;

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


          我們可以看到UserService類沒有定義@Service注解,即沒有交給spring管理bean實(shí)例,所以它的add方法也不會(huì)生成事務(wù)。


          5.錯(cuò)誤的spring事務(wù)傳播特性





              
                  @Service
          public?class?UserService?{

          ????@Autowired
          ????private?UserMapper userMapper;

          ????@Transactional(propagation = Propagation.NEVER)
          ????public?void?add(UserModel userModel)?{
          ????????userMapper.insertUser(userModel);
          ????}

          }


          我們可以看到add方法的事務(wù)傳播特性定義成了Propagation.NEVER,這種類型的傳播特性不支持事務(wù),如果有事務(wù)則會(huì)拋異常。只有這三種傳播特性才會(huì)創(chuàng)建新事務(wù):PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。


          6.數(shù)據(jù)庫(kù)不支持事務(wù)





          mysql8以前的版本數(shù)據(jù) 庫(kù)引擎是支持myISAM 和innodb的。 我以前也用過(guò),對(duì)應(yīng)查多寫少的單表操作,可能會(huì)把表的數(shù)據(jù)庫(kù)引擎定義成mysISAM,這樣可以提升查詢效率。 但是,要千萬(wàn)記得一件事情,myISAM只支持表鎖,并且不支持事務(wù)。 所以,對(duì)這類表的寫入操作事務(wù)會(huì)失效。


          7.自己吞掉了異常





              
                  @Slf4j
          @Service
          public class UserService {

          ????@Autowired
          ????private UserMapper userMapper;
          ????
          ????@Transactional
          ????public void add(UserModel userModel) {
          ????????try?{
          ????????????userMapper.insertUser(userModel);
          ????????}?catch?(Exception e) {
          ????????????log.error(e.getMessage(), e);
          ????????}
          ????}
          }


          這種情況下事務(wù)不會(huì)回滾,因?yàn)殚_發(fā)者自己捕獲了異常,又沒有拋出。事務(wù)的AOP無(wú)法捕獲異常,導(dǎo)致即使出現(xiàn)了異常,事務(wù)也不會(huì)回滾。


          8.拋出的異常不正確





              
                  @Slf4j
          @Service
          public class UserService {

          ????@Autowired
          ????private UserMapper userMapper;
          ????
          ????@Transactional
          ????public void add(UserModel userModel) throws Exception {
          ????????try?{
          ????????????userMapper.insertUser(userModel);
          ????????}?catch?(Exception e) {
          ????????????log.error(e.getMessage(), e);
          ????????????throw?new?Exception(e);
          ????????}
          ????}

          }


          這種情況下,開發(fā)人員自己捕獲了異常,又拋出了異常:Exception,事務(wù)也不會(huì)回滾。因?yàn)閟pring事務(wù),默認(rèn)情況下只會(huì)回滾RuntimeException(運(yùn)行時(shí)異常)和Error(錯(cuò)誤),不會(huì)回滾Exception。


          9.多線程調(diào)用





                @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表數(shù)據(jù)");
          ????}
          }

          我們可以看到事務(wù)方法add中,調(diào)用了事務(wù)方法doOtherThing,但是事務(wù)方法doOtherThing是在另外一個(gè)線程中調(diào)用的,這樣會(huì)導(dǎo)致兩個(gè)事務(wù)方法不在同一個(gè)線程中,獲取到的數(shù)據(jù)庫(kù)連接不一樣,從而是兩個(gè)不同的事務(wù)。如果想doOtherThing方法中拋了異常,add方法也回滾是不可能的。

          如果看過(guò)spring事務(wù)源碼的朋友,可能會(huì)知道spring的事務(wù)是通過(guò)數(shù)據(jù)庫(kù)連接來(lái)實(shí)現(xiàn)的。當(dāng)前線程中保存了一個(gè)map,key是數(shù)據(jù)源,value是數(shù)據(jù)庫(kù)連接。

              
                  private?static?final ThreadLocal<Map<Object,?Object>> resources =
          ??????new?NamedThreadLocal<>("Transactional resources");


          我們說(shuō)的同一個(gè)事務(wù),其實(shí)是指同一個(gè)數(shù)據(jù)庫(kù)連接,只有擁有同一個(gè)數(shù)據(jù)庫(kù)連接才能同時(shí)提交和回滾。如果在不同的線程,拿到的數(shù)據(jù)庫(kù)連接肯定是不一樣的,所以是不同的事務(wù)。


          10.嵌套事務(wù)多回滾了





                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表數(shù)據(jù)");
          ????}
          }

          這種情況使用了嵌套的內(nèi) 部事務(wù),原本是希望調(diào)用roleService.doOtherThing方法時(shí),如果出現(xiàn)了異常,只回滾doOtherThing方法里的內(nèi)容,不回滾 userMapper.insertUser里的內(nèi)容,即回滾保存點(diǎn)。 但事實(shí)是,insertUser也回滾了。

          why?

          因?yàn)閐oOtherThing方法出現(xiàn)了異常,沒有手動(dòng)捕獲,會(huì)繼續(xù)往上拋,到外層add方法的代理方法中捕獲了異常。所以,這種情況是直接回滾了整個(gè)事務(wù),不只回滾單個(gè)保存點(diǎn)。

          怎么樣才能只回滾保存點(diǎn)呢?

              
                  @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);
          ????????}
          ????}

          }


          在代碼中手動(dòng)把內(nèi)部嵌套事務(wù)放在try/catch中,并且不繼續(xù)往拋異常。

          介紹到這里,你會(huì)發(fā)現(xiàn)spring事務(wù)的坑還是挺多的~

          文章來(lái)源:https://juejin.cn/post/6863796940069568520



          如果你覺得這篇文 章不錯(cuò),那么,下篇通常會(huì)更好。 備注“公眾號(hào)”添加微信好友 (微信號(hào):zhuan2quan)

          ▲? 長(zhǎng) 按關(guān) 注”程序 新視界“,洞察技術(shù)內(nèi)幕


          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品酒店在线观看视频成人电影 | 影音先锋av成人网 | 麻豆国产精品视频 | 成人免费看黄片 | 西西人体大胆4444w w |