事務(wù)失效

目錄
一哮伟、訪問權(quán)限問題
二干花、方法用final修飾
三、方法內(nèi)部調(diào)用
四楞黄、末被spring管理
五池凄、多線程調(diào)用
六、表不支持事務(wù)
七鬼廓、未開啟事務(wù)
八肿仑、 錯(cuò)誤的傳播特性 / 異常
九、聲明式事務(wù)和編程式事務(wù)

一.訪問權(quán)限問題

例子

@Service
public class UserService {
    
    @Transactional
    private void add(UserModel userModel) {
         saveData(userModel);
         updateData(userModel);
    }
}

分析

1.spring要求被代理方法必須是public的碎税。
2.java的訪問權(quán)限主要有四種:private尤慰、default、protected蚣录、public,它們的權(quán)限從左到右眷篇,依次變大
3.在AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個(gè)判斷萎河,如果目標(biāo)方法不是public,則TransactionAttribute返回null蕉饼,即不支持事務(wù)虐杯。
4.AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
    // 省略.................

二.方法用final修飾

例子

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

分析

1.add方法被定義成了final的,這樣會導(dǎo)致事務(wù)失效昧港。
2.spring事務(wù)底層使用了aop擎椰,也就是通過jdk動態(tài)代理或者cglib,幫我們生成了代理類创肥,在代理類中實(shí)現(xiàn)的事務(wù)功能达舒。如果某個(gè)方法用final修飾了,那么在它的代理類中叹侄,就無法重寫該方法巩搏,而添加事務(wù)功能。
3.注意:如果某個(gè)方法是static的趾代,同樣無法通過動態(tài)代理贯底,變成事務(wù)方法。

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

例子

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

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

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

分析

1.需要在某個(gè)Service類的某個(gè)方法中撒强,調(diào)用另外一個(gè)事務(wù)方法禽捆,這樣會導(dǎo)致事務(wù)失效。
2.updateStatus方法擁有事務(wù)的能力是因?yàn)閟pring aop生成代理了對象, 但是這種方法直接調(diào)用了this對象的方法, 所以updateStatus方法不會生成事務(wù)
3.如果想在同一個(gè)類某個(gè)方法調(diào)用自己另一個(gè)事務(wù)方法, 有什么辦法嗎?
3.1 新增一個(gè)Service類方法, 把@Transactional注解加到新Service方法上,具體代碼如下
@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();
    }

 }
 
 
3.2 該Service類中注入自己
 @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();
    }
 }
 
3.3 該Service類中使用AopContext.currentProxy()獲取代理對象
@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();
    }
 }
 
 
4.其實(shí)spring ioc內(nèi)部的三級緩存保證了它飘哨,不會出現(xiàn)循環(huán)依賴問題胚想。
spring:我是如何解決循環(huán)依賴的: 
https://mp.weixin.qq.com/s__biz=MzkwNjMwMTgzMQ==&mid=2247490271&idx=1&sn=e4476b631c48882392bd4cd06d579ae9&source=41#wechat_redirect

四.末被spring管理

例子

//@Service
public class UserService {

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

分析

1.忘了加@Service注解, 使用spring事務(wù)的前提是:對象要被spring管理,需要創(chuàng)建bean實(shí)例芽隆。

五.多線程調(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ù)");
    }
}

分析

1.事務(wù)方法add中顿仇,調(diào)用了事務(wù)方法doOtherThing淘正,但是事務(wù)方法doOtherThing是在另外一個(gè)線程中調(diào)用的
2.兩個(gè)方法不在同一個(gè)線程中,獲取到的數(shù)據(jù)庫連接不一樣,想doOtherThing方法中拋了異常臼闻,add方法也回滾是不可能的鸿吆。
3.spring的事務(wù)是通過數(shù)據(jù)庫連接實(shí)現(xiàn)的, 當(dāng)前線程保存了map, key是數(shù)據(jù)源,value是數(shù)據(jù)庫連接述呐。
private static final ThreadLocal<Map<Object, Object>> resources =
  new NamedThreadLocal<>("Transactional resources");

六.表不支持事務(wù)

分析

1.在mysql5之前惩淳,默認(rèn)的數(shù)據(jù)庫引擎是myisam,myisam好用,但有個(gè)很致命的問題是:不支持事務(wù)乓搬。
2.myisam好處:索引文件和數(shù)據(jù)文件是分開存儲, 對于查多寫少的單表操作, 性能比innodb好

七.未開啟事務(wù)

分析

1.事務(wù)沒有生效的根本原因是沒有開啟事務(wù)思犁。
2.springboot項(xiàng)目: springboot通過DataSourceTransactionManagerAutoConfiguration類已經(jīng)幫你默默開啟了事務(wù),只需要配置spring.datasource相關(guān)參數(shù)
3.spring.datasource
<!-- 配置事務(wù)管理器 --> 
<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> 
<!-- 用切點(diǎn)把事務(wù)切進(jìn)去 --> 
<aop:config> 
    <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> 
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

八.錯(cuò)誤的傳播特性 / 異常

例子

// case1
@Service
public class UserService {

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

// case2
@Slf4j
@Service
public class UserService {
    
    @Transactional
    public void add(UserModel userModel) {
        try {
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

// case3
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        // 期望結(jié)果: 即使doOtherThing異常, 上面insertUser依然正常
        // 錯(cuò)誤
        roleService.doOtherThing();
        // 正確
        try {
            roleService.doOtherThing();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

@Service
public class RoleService {

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

分析

1.事務(wù)的傳播特性
REQUIRED 如果當(dāng)前上下文中存在事務(wù),那么加入該事務(wù)进肯,如果不存在事務(wù)激蹲,創(chuàng)建一個(gè)事務(wù),這是默認(rèn)的傳播屬性值江掩。
SUPPORTS 如果當(dāng)前上下文存在事務(wù)学辱,則支持事務(wù)加入事務(wù),如果不存在事務(wù)环形,則使用非事務(wù)的方式執(zhí)行策泣。
MANDATORY 如果當(dāng)前上下文中存在事務(wù),否則拋出異常抬吟。
REQUIRES_NEW 每次都會新建一個(gè)事務(wù)萨咕,并且同時(shí)將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后火本,上下文事務(wù)恢復(fù)再執(zhí)行危队。
NOT_SUPPORTED 如果當(dāng)前上下文中存在事務(wù),則掛起當(dāng)前事務(wù)钙畔,然后新的方法在沒有事務(wù)的環(huán)境中執(zhí)行交掏。
NEVER 如果當(dāng)前上下文中存在事務(wù),則拋出異常刃鳄,否則在無事務(wù)環(huán)境上執(zhí)行代碼盅弛。
NESTED 如果當(dāng)前上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行叔锐,如果不存在事務(wù)挪鹏,則新建事務(wù)。

2.case1: add方法的事務(wù)傳播特性定義成了Propagation.NEVER愉烙,這種類型的傳播特性不支持事務(wù)讨盒,如果有事務(wù)則會拋異常。
3.case2: 自己手動try...catch吞了異常, 這種情況下spring事務(wù)當(dāng)然不會回滾
注意1: 默認(rèn)情況下只會回滾RuntimeException(運(yùn)行時(shí)異常)和Error(錯(cuò)誤)步责,對于普通的Exception(非運(yùn)行時(shí)異常)返顺,它不會回滾
注意2: 自定義了回滾異常,@Transactional(rollbackFor = BusinessException.class), 如果程序報(bào)錯(cuò)了禀苦,拋了SqlException、DuplicateKeyException等異常遂鹊。而BusinessException是我們自定義的異常振乏,報(bào)錯(cuò)的異常不屬于BusinessException,所以事務(wù)也不會回滾秉扑。建議一般情況下慧邮,將該參數(shù)設(shè)置成:Exception或Throwable。
4.case3:嵌套事務(wù)回滾多了

九.聲明式事務(wù)和編程式事務(wù)

例子

 @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         queryData1();
         queryData2();
         transactionTemplate.execute((status) => {
            addData1();
            updateData2();
            return Boolean.TRUE;
         })
   }

分析

1.聲明式事務(wù)和編程式事務(wù)
聲明式事務(wù):基于@Transactional注解
編程式事務(wù):手動編寫代碼實(shí)現(xiàn)的事務(wù)
2.TransactionTemplate
(1)在spring中為了支持編程式事務(wù)舟陆,專門提供了一個(gè)類:TransactionTemplate误澳,在它的execute方法中,就實(shí)現(xiàn)了事務(wù)的功能
(2)好處:
--避免由于spring aop問題導(dǎo)致事務(wù)失效的問題
--可以能夠更小粒度控制事務(wù)的范圍
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秦躯,一起剝皮案震驚了整個(gè)濱河市忆谓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踱承,老刑警劉巖倡缠,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異勾扭,居然都是意外死亡毡琉,警方通過查閱死者的電腦和手機(jī)铁瞒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門妙色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慧耍,你說我怎么就攤上這事身辨。” “怎么了芍碧?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵煌珊,是天一觀的道長。 經(jīng)常有香客問我泌豆,道長定庵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任踪危,我火速辦了婚禮蔬浙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贞远。我一直安慰自己畴博,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布蓝仲。 她就那樣靜靜地躺著俱病,像睡著了一般官疲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亮隙,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天途凫,我揣著相機(jī)與錄音,去河邊找鬼咱揍。 笑死颖榜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的煤裙。 我是一名探鬼主播掩完,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼硼砰!你這毒婦竟也來了且蓬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤题翰,失蹤者是張志新(化名)和其女友劉穎恶阴,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豹障,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冯事,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了血公。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昵仅。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖累魔,靈堂內(nèi)的尸體忽然破棺而出摔笤,到底是詐尸還是另有隱情,我是刑警寧澤垦写,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布吕世,位于F島的核電站,受9級特大地震影響梯投,放射性物質(zhì)發(fā)生泄漏命辖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一分蓖、第九天 我趴在偏房一處隱蔽的房頂上張望尔艇。 院中可真熱鬧,春花似錦咆疗、人聲如沸漓帚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尝抖。三九已至毡们,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昧辽,已是汗流浹背衙熔。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搅荞,地道東北人红氯。 一個(gè)月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像咕痛,于是被迫代替她去往敵國和親痢甘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容