<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 聲明式事務

          共 5906字,需瀏覽 12分鐘

           ·

          2023-07-14 13:43

          1. 前言

          Spring 針對 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API (JPA) 等事務 API,實現(xiàn)了一致的編程模型,我們大多數(shù)做業(yè)務開發(fā)的時候,通常就在業(yè)務方法上使用聲明式注解 @Transactional 來開啟事務,大多數(shù)我們就沒有去關注事務是否會生效,出錯后事務是否能正確回滾,所以這里是有“坑”的。 事務沒有正確處理,對于我們來說通常是不易發(fā)現(xiàn)的,當壓力越來越大數(shù)據(jù)越來越多的時候,極有可能帶來大量的數(shù)據(jù)不一致臟數(shù)據(jù)的問題,所以處理好事務極為重要。

          2. Spring事務沒有生效

          首先來看一下,Spring 在什么情況下事務是不生效的,在這里為了方便我直接就采用了Sping JPA 作為數(shù)據(jù)庫訪問,首先定義一個實體類

              @Entity
          @Data
          @NoArgsConstructor
          public?class?SysUser?{

          ????@Id
          ????@GeneratedValue(strategy?=?GenerationType.AUTO)
          ????private?Long?id;
          ????private?String?name;

          ????public?SysUser(String?name)?{
          ????????this.name?=?name;
          ????}
          }

          實現(xiàn)一個repository 接口,里面有一個根據(jù)名字查SysUser的方法

              @Repository
          public?interface?SysUserRepository?extends?JpaRepository<SysUser,?Long>?{
          ????List<SysUser>?findByName(String?name);
          }

          實現(xiàn)一個SysUserService,其中使用一個公有方法調(diào)用標記了 @Transactional 注解的私有方法

              @Service
          @Slf4j
          public?class?SysUserService?{
          ????@Resource
          ????private?SysUserRepository?sysUserRepository;

          ????/**
          ?????*?公有方法調(diào)用標記了?@Transactional?注解的私有方法
          ?????*?@param?name
          ?????*?@return
          ?????*/

          ????public?int?createUserWrong(String?name)?{
          ????????this.createUserPrivate(new?SysUser(name));
          ????????return?this.sysUserRepository.findByName(name).size();
          ????}

          ????@Transactional
          ????private?void?createUserPrivate(SysUser?sysUser)?{
          ????????this.sysUserRepository.save(sysUser);
          ????????if?(sysUser.getName().contains("test"))?{
          ????????????throw?new?RuntimeException("invalid?name");
          ????????}
          ????}
          }

          實現(xiàn)一個Controller如下

              @RestController
          @RequestMapping("/proxyfailed")
          @RequiredArgsConstructor
          public?class?ProxyFailedController?{

          ????private?final?SysUserService?sysUserService;

          ????@GetMapping("/wrong1")
          ????public?int?wrong1(@RequestParam?String?name)?{
          ????????return?this.sysUserService.createUserWrong1(name);
          ????}
          }

          測試接口可以發(fā)現(xiàn),程序報異常了,但是數(shù)據(jù)庫已經(jīng)卻成功的插入了記錄,事務并未生效!!!

          dd85372cf8b965d0919380cb5709c03d.webp
          422bf7bf82b2e154609c9e30511ec34e.webp

          其實在上面已經(jīng)看出來了,idea會在當你使用@Transactional 標記 private 修飾的方法時報紅。 @Transactional生效的原則之一就是,只有定義在public方法上的 @Transactional 注解才能生效。這是因為Spring 默認使用動態(tài)代理實現(xiàn)AOP,對目標方法進行增強,private 修飾的方法是無法被代理到的。 那如果說,我把上面的 createUserPrivate 方法改為 public 修飾,那么事務是否會生效呢?

          7b62b08bcaa634a56d5f771c340cd977.webp

          答案是否定的,事務是依然不會生效的。要使 @Transactional生效的原則之二就是,必須通過代理類從外部調(diào)用目標方法才能生效。在這里,使用this調(diào)用目標方法,this指向的并不是代理類,而是當前目標類實例。 在這里可以在目標Service類注入自己的Bean 實例,如下:

              ??@Resource
          ????private?SysUserService?sysUserService;
          ????public?int?createUserRight1(String?name)?{
          ????????this.sysUserService.createUserPublic(new?SysUser(name));
          ????????return?this.sysUserRepository.findByName(name).size();
          ????}
          837589a0797c0b9e8421901e16b3f5f3.webp

          可以看到此時 this.sysUserService是通過cglib增強過的代理類實例,所以此時 @Transactional 注解是生效的。但是自己注入自己是一件很怪的事情,最好還是在controller中直接調(diào)用被 @Transactional標記的 public 方法,使事務生效,這里可以看到this.sysUserService同樣是被增強后的代理類

          b0642ca54c3214d8159f0473f9d69cd3.webp

          那接下來我們看看下面這種情況, @Transactional有沒有生效,也就是標記了 @Transactional的public方法調(diào)用private修飾的方法,且在private方法中進行了數(shù)據(jù)庫操作

              ?@GetMapping("/right3")
          ????public?int?right3(@RequestParam?String?name)?{
          ????????return?this.sysUserService.createUserRight3(name);
          ????}
          ?@Transactional
          ????public?int?createUserRight3(String?name)?{
          ????????this.createUserPrivate1(new?SysUser(name));
          ????????return?this.sysUserRepository.findByName(name).size();
          ????}

          ????private?void?createUserPrivate1(SysUser?sysUser)?{
          ????????this.sysUserRepository.save(sysUser);
          ????????if?(sysUser.getName().contains("test"))?{
          ????????????throw?new?RuntimeException("invalid?name");
          ????????}
          ????}

          答案是事務是生效的,因為在controller層調(diào)用createUserRight3方法,是通過代理對象調(diào)用的,在這時已經(jīng)開啟了事務,接下來在createUserRight3方法中的createUserPrivate1方法的調(diào)用只不過對應著線程中棧幀的壓棧,事務已經(jīng)在前面開啟了。對應之前事務不生效的幾種情況是它們的事務就根本沒開啟。

          3. Spring事務沒有回滾

          上面講了 Spring 聲明式事務未生效的幾種情況,下面來談一談事務沒有回滾的幾種情況,也就是說事務生效了,但是事務(對應于數(shù)據(jù)庫數(shù)據(jù))并沒有回滾的情況。

              @RestController
          @RequestMapping("/rollbackfailed")
          @RequiredArgsConstructor
          public?class?RollbackFailedController?{

          ????private?final?SysUserService?sysUserService;

          ????@GetMapping("/wrong1")
          ????public?void?wrong1(@RequestParam?String?name)?{
          ????????this.sysUserService.createUserWrong1(name);
          ????}
          }
          @Service
          @Slf4j
          public?class?SysUserService?{
          ????@Resource
          ????private?SysUserRepository?sysUserRepository;

          ????@Transactional
          ????public?void?createUserWrong1(String?name)?{
          ????????try?{
          ????????????sysUserRepository.save(new?SysUser(name));
          ????????????throw?new?RuntimeException("error");
          ????????}?catch?(Exception?e)?{
          ????????????log.error("create?user?failed",?e);
          ????????}
          ????}
          }

          我們可以看到在createUserWrong1方法中捕獲了異常,但是在 catch 塊處理中只是打印了錯誤日志,在這里事務并不會回滾。默認情況下,出現(xiàn) RuntimeException(非受檢異常)或 Error 的時候,Spring 才會回滾事務。 也就是說對于上面的情況,你需要在catch 塊中手動的去回滾事務,或者你干脆不捕獲異常

              ???@Transactional
          ????public?void?createUserWrong1(String?name)?{
          ????????try?{
          ????????????sysUserRepository.save(new?SysUser(name));
          ????????????throw?new?RuntimeException("error");
          ????????}?catch?(Exception?e)?{
          ????????????log.error("create?user?failed",?e);
          ????????????TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
          ????????}
          ????}

          上面說了,默認情況下,出現(xiàn) RuntimeException(非受檢異常)或 Error 的時候,Spring 才會回滾事務。下面的情況是檢查時異常,這種情況下,是不會回滾事務的。

              ?@Transactional
          ????public?void?createUserWrong2(String?name)?throws?IOException?{
          ????????sysUserRepository.save(new?SysUser(name));
          ????????readFile();
          ????}

          ????/**
          ?????*?檢查時異常
          ?????*?@throws?IOException
          ?????*/

          ????private?void?readFile()?throws?IOException?{
          ????????Files.readAllLines(Paths.get("file-that-not-exist"));
          ????}

          想要遇到所有的 Exception 都回滾事務,需要在 @Transactional注解中添加屬性

              ?@Transactional(rollbackFor?=?Exception.class)
          ????public?void?createUserWrong2(String?name)?throws?IOException?{
          ????????sysUserRepository.save(new?SysUser(name));
          ????????readFile();
          ????}

          4. 總結

          針對 Spring 聲明式事務生效,需要保證:

          • ??@Transactional生效的原則之一就是,只有定義在public方法上的 @Transactional 注解才能生效。

          • ??要使 @Transactional生效的原則之二就是,必須通過代理類從外部調(diào)用目標方法才能生效。

          針對 Spring 聲明式事務回滾,需要注意:

          • ??默認情況下,出現(xiàn) RuntimeException(非受檢異常)或 Error 的時候,Spring 才會回滾事務。

          • ??要使檢查時異常也回滾,考慮設置@Transactional屬性


          瀏覽 59
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产高清无码内射 | 一级色网| 国产一二三在线观看 | 国产伦精品一区二区三区最新章节 | 午夜精品久久久久久久蜜桃麻豆视 |