1. 聊聊spring事務(wù)失效的12種場(chǎng)景,太坑了

        共 12545字,需瀏覽 26分鐘

         ·

        2021-12-23 03:54

        前言

        對(duì)于從事java開(kāi)發(fā)工作的同學(xué)來(lái)說(shuō),spring的事務(wù)肯定再熟悉不過(guò)了。

        在某些業(yè)務(wù)場(chǎng)景下,如果一個(gè)請(qǐng)求中,需要同時(shí)寫(xiě)入多張表的數(shù)據(jù)。為了保證操作的原子性(要么同時(shí)成功,要么同時(shí)失?。苊鈹?shù)據(jù)不一致的情況,我們一般都會(huì)用到spring事務(wù)。

        確實(shí),spring事務(wù)用起來(lái)賊爽,就用一個(gè)簡(jiǎn)單的注解:@Transactional,就能輕松搞定事務(wù)。我猜大部分小伙伴也是這樣用的,而且一直用一直爽。

        但如果你使用不當(dāng),它也會(huì)坑你于無(wú)形。

        今天我們就一起聊聊,事務(wù)失效的一些場(chǎng)景,說(shuō)不定你已經(jīng)中招了。不信,讓我們一起看看。

        一 事務(wù)不生效

        1.訪問(wèn)權(quán)限問(wèn)題

        眾所周知,java的訪問(wèn)權(quán)限主要有四種:private、default、protected、public,它們的權(quán)限從左到右,依次變大。

        但如果我們?cè)陂_(kāi)發(fā)過(guò)程中,把有某些事務(wù)方法,定義了錯(cuò)誤的訪問(wèn)權(quán)限,就會(huì)導(dǎo)致事務(wù)功能出問(wèn)題,例如:

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

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

        說(shuō)白了,在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;
        ??}

        也就是說(shuō),如果我們自定義的事務(wù)方法(即目標(biāo)方法),它的訪問(wèn)權(quán)限不是public,而是private、default或protected的話,spring則不會(huì)提供事務(wù)功能。

        2. 方法用final修飾

        有時(shí)候,某個(gè)方法不想被子類重新,這時(shí)可以將該方法定義成final的。普通方法這樣定義是沒(méi)問(wèn)題的,但如果將事務(wù)方法定義成final,例如:

        @Service
        public?class?UserService?{

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

        我們可以看到add方法被定義成了final的,這樣會(huì)導(dǎo)致事務(wù)失效。

        為什么?

        如果你看過(guò)spring事務(wù)的源碼,可能會(huì)知道spring事務(wù)底層使用了aop,也就是通過(guò)jdk動(dòng)態(tài)代理或者cglib,幫我們生成了代理類,在代理類中實(shí)現(xiàn)的事務(wù)功能。

        但如果某個(gè)方法用final修飾了,那么在它的代理類中,就無(wú)法重寫(xiě)該方法,而添加事務(wù)功能。

        注意:如果某個(gè)方法是static的,同樣無(wú)法通過(guò)動(dòng)態(tài)代理,變成事務(wù)方法。

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

        有時(shí)候我們需要在某個(gè)Service類的某個(gè)方法中,調(diào)用另外一個(gè)事務(wù)方法,比如:

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

        我們看到在事務(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ù)。

        由此可見(jiàn),在同一個(gè)類中的方法直接內(nèi)部調(diào)用,會(huì)導(dǎo)致事務(wù)失效。

        那么問(wèn)題來(lái)了,如果有些場(chǎng)景,確實(shí)想在同一個(gè)類的某個(gè)方法中,調(diào)用它自己的另外一個(gè)方法,該怎么辦呢?

        3.1 新加一個(gè)Service方法

        這個(gè)方法非常簡(jiǎn)單,只需要新加一個(gè)Service方法,把@Transactional注解加到新Service方法上,把需要事務(wù)執(zhí)行的代碼移到新方法中。具體代碼如下:

        @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類中注入自己

        如果不想再新加一個(gè)Service類,在該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();
        ????}
        ?}

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

        答案:不會(huì)。

        其實(shí)spring ioc內(nèi)部的三級(jí)緩存保證了它,不會(huì)出現(xiàn)循環(huán)依賴問(wèn)題。但有些坑,如果你想進(jìn)一步了解循環(huán)依賴問(wèn)題,可以看看我之前文章《spring:我是如何解決循環(huán)依賴的?》。

        3.3 通過(guò)AopContent類

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

        上面的方法2確實(shí)可以解決問(wèn)題,但是代碼看起來(lái)并不直觀,還可以通過(guò)在該Service類中使用AOPProxy獲取代理對(duì)象,實(shí)現(xiàn)相同的功能。具體代碼如下:

        @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.未被spring管理

        在我們平時(shí)開(kāi)發(fā)過(guò)程中,有個(gè)細(xì)節(jié)很容易被忽略。即使用spring事務(wù)的前提是:對(duì)象要被spring管理,需要?jiǎng)?chuàng)建bean實(shí)例。

        通常情況下,我們通過(guò)@Controller、@Service、@Component、@Repository等注解,可以自動(dòng)實(shí)現(xiàn)bean實(shí)例化和依賴注入的功能。

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

        如果有一天,你匆匆忙忙的開(kāi)發(fā)了一個(gè)Service類,但忘了加@Service注解,比如:

        //@Service
        public?class?UserService?{

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

        從上面的例子,我們可以看到UserService類沒(méi)有加@Service注解,那么該類不會(huì)交給spring管理,所以它的add方法也不會(huì)生成事務(wù)。

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

        在實(shí)際項(xiàng)目開(kāi)發(fā)中,多線程的使用場(chǎng)景還是挺多的。如果spring事務(wù)用在多線程場(chǎng)景中,會(huì)有問(wè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);
        ????????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è)方法不在同一個(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>?resources?=

        ??new?NamedThreadLocal<>("Transactional?resources");

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

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

        周所周知,在mysql5之前,默認(rèn)的數(shù)據(jù)庫(kù)引擎是myisam。

        它的好處就不用多說(shuō)了:索引文件和數(shù)據(jù)文件是分開(kāi)存儲(chǔ)的,對(duì)于查多寫(xiě)少的單表操作,性能比innodb更好。

        有些老項(xiàng)目中,可能還在用它。

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

        CREATE?TABLE?`category`?(
        ??`id`?bigint?NOT?NULL?AUTO_INCREMENT,
        ??`one_category`?varchar(20)?COLLATE?utf8mb4_bin?DEFAULT?NULL,
        ??`two_category`?varchar(20)?COLLATE?utf8mb4_bin?DEFAULT?NULL,
        ??`three_category`?varchar(20)?COLLATE?utf8mb4_bin?DEFAULT?NULL,
        ??`four_category`?varchar(20)?COLLATE?utf8mb4_bin?DEFAULT?NULL,
        ??PRIMARY?KEY?(`id`)
        )?ENGINE
        =MyISAM?AUTO_INCREMENT=4?DEFAULT?CHARSET=utf8mb4?COLLATE=utf8mb4_bin

        myisam好用,但有個(gè)很致命的問(wèn)題是:不支持事務(wù)。

        如果只是單表操作還好,不會(huì)出現(xiàn)太大的問(wèn)題。但如果需要跨多張表操作,由于其不支持事務(wù),數(shù)據(jù)極有可能會(huì)出現(xiàn)不完整的情況。

        此外,myisam還不支持行鎖和外鍵。

        所以在實(shí)際業(yè)務(wù)場(chǎng)景中,myisam使用的并不多。在mysql5以后,myisam已經(jīng)逐漸退出了歷史的舞臺(tái),取而代之的是innodb。

        有時(shí)候我們?cè)陂_(kāi)發(fā)的過(guò)程中,發(fā)現(xiàn)某張表的事務(wù)一直都沒(méi)有生效,那不一定是spring事務(wù)的鍋,最好確認(rèn)一下你使用的那張表,是否支持事務(wù)。

        7.未開(kāi)啟事務(wù)

        有時(shí)候,事務(wù)沒(méi)有生效的根本原因是沒(méi)有開(kāi)啟事務(wù)。

        你看到這句話可能會(huì)覺(jué)得好笑。

        開(kāi)啟事務(wù)不是一個(gè)項(xiàng)目中,最最最基本的功能嗎?

        為什么還會(huì)沒(méi)有開(kāi)啟事務(wù)?

        沒(méi)錯(cuò),如果項(xiàng)目已經(jīng)搭建好了,事務(wù)功能肯定是有的。

        但如果你是在搭建項(xiàng)目demo的時(shí)候,只有一張表,而這張表的事務(wù)沒(méi)有生效。那么會(huì)是什么原因造成的呢?

        當(dāng)然原因有很多,但沒(méi)有開(kāi)啟事務(wù),這個(gè)原因極其容易被忽略。

        如果你使用的是springboot項(xiàng)目,那么你很幸運(yùn)。因?yàn)閟pringboot通過(guò)DataSourceTransactionManagerAutoConfiguration類,已經(jīng)默默的幫你開(kāi)啟了事務(wù)。

        你所要做的事情很簡(jiǎn)單,只需要配置spring.datasource相關(guān)參數(shù)即可。

        但如果你使用的還是傳統(tǒng)的spring項(xiàng)目,則需要在applicationContext.xml文件中,手動(dòng)配置事務(wù)相關(guān)參數(shù)。如果忘了配置,事務(wù)肯定是不會(huì)生效的。

        具體配置如下信息:

        ???
        ?
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"?id="transactionManager">?
        ????"dataSource"?ref="dataSource">?
        ?
        "advice"?transaction-manager="transactionManager">?
        ?????
        ????????"*"?propagation="REQUIRED"/>
        ????
        ?
        ?
        ?
        ?
        ????"execution(*?com.susan.*.*(..))"?id="pointcut"/>?
        ????"advice"?pointcut-ref="pointcut"/>?
        ?

        默默的說(shuō)一句,如果在pointcut標(biāo)簽中的切入點(diǎn)匹配規(guī)則,配錯(cuò)了的話,有些類的事務(wù)也不會(huì)生效。

        二 事務(wù)不回滾

        1.錯(cuò)誤的傳播特性

        其實(shí),我們?cè)谑褂?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">@Transactional注解時(shí),是可以指定propagation參數(shù)的。

        該參數(shù)的作用是指定事務(wù)的傳播特性,spring目前支持7種傳播特性:

        • 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 每次都會(huì)新建一個(gè)事務(wù),并且同時(shí)將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)恢復(fù)再執(zhí)行。
        • NOT_SUPPORTED 如果當(dāng)前上下文中存在事務(wù),則掛起當(dāng)前事務(wù),然后新的方法在沒(méi)有事務(wù)的環(huán)境中執(zhí)行。
        • NEVER 如果當(dāng)前上下文中存在事務(wù),則拋出異常,否則在無(wú)事務(wù)環(huán)境上執(zhí)行代碼。
        • NESTED 如果當(dāng)前上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行,如果不存在事務(wù),則新建事務(wù)。

        如果我們?cè)谑謩?dòng)設(shè)置propagation參數(shù)的時(shí)候,把傳播特性設(shè)置錯(cuò)了,比如:

        @Service
        public?class?UserService?{

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

        我們可以看到add方法的事務(wù)傳播特性定義成了Propagation.NEVER,這種類型的傳播特性不支持事務(wù),如果有事務(wù)則會(huì)拋異常。

        目前只有這三種傳播特性才會(huì)創(chuàng)建新事務(wù):REQUIRED,REQUIRES_NEW,NESTED。

        2.自己吞了異常

        事務(wù)不會(huì)回滾,最常見(jiàn)的問(wèn)題是:開(kāi)發(fā)者在代碼中手動(dòng)try...catch了異常。比如:

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

        這種情況下spring事務(wù)當(dāng)然不會(huì)回滾,因?yàn)殚_(kāi)發(fā)者自己捕獲了異常,又沒(méi)有手動(dòng)拋出,換句話說(shuō)就是把異常吞掉了。

        如果想要spring事務(wù)能夠正?;貪L,必須拋出它能夠處理的異常。如果沒(méi)有拋異常,則spring認(rèn)為程序是正常的。

        3.手動(dòng)拋了別的異常

        即使開(kāi)發(fā)者沒(méi)有手動(dòng)捕獲異常,但如果拋的異常不正確,spring事務(wù)也不會(huì)回滾。

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

        上面的這種情況,開(kāi)發(fā)人員自己捕獲了異常,又手動(dòng)拋出了異常:Exception,事務(wù)同樣不會(huì)回滾。

        因?yàn)閟pring事務(wù),默認(rèn)情況下只會(huì)回滾RuntimeException(運(yùn)行時(shí)異常)和Error(錯(cuò)誤),對(duì)于普通的Exception(非運(yùn)行時(shí)異常),它不會(huì)回滾。

        4.自定義了回滾異常

        在使用@Transactional注解聲明事務(wù)時(shí),有時(shí)我們想自定義回滾的異常,spring也是支持的??梢酝ㄟ^(guò)設(shè)置rollbackFor參數(shù),來(lái)完成這個(gè)功能。

        但如果這個(gè)參數(shù)的值設(shè)置錯(cuò)了,就會(huì)引出一些莫名其妙的問(wèn)題,例如:

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

        如果在執(zhí)行上面這段代碼,保存和更新數(shù)據(jù)時(shí),程序報(bào)錯(cuò)了,拋了SqlException、DuplicateKeyException等異常。而B(niǎo)usinessException是我們自定義的異常,報(bào)錯(cuò)的異常不屬于BusinessException,所以事務(wù)也不會(huì)回滾。

        即使rollbackFor有默認(rèn)值,但阿里巴巴開(kāi)發(fā)者規(guī)范中,還是要求開(kāi)發(fā)者重新指定該參數(shù)。

        這是為什么呢?

        因?yàn)槿绻褂媚J(rèn)值,一旦程序拋出了Exception,事務(wù)不會(huì)回滾,這會(huì)出現(xiàn)很大的bug。所以,建議一般情況下,將該參數(shù)設(shè)置成:Exception或Throwable。

        5.嵌套事務(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)了異常,沒(méi)有手動(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);
        ????????}
        ????}
        }

        可以將內(nèi)部嵌套事務(wù)放在try/catch中,并且不繼續(xù)往上拋異常。這樣就能保證,如果內(nèi)部嵌套事務(wù)中出現(xiàn)異常,只回滾內(nèi)部事務(wù),而不影響外部事務(wù)。

        三 其他

        1 大事務(wù)問(wèn)題

        在使用spring事務(wù)時(shí),有個(gè)讓人非常頭疼的問(wèn)題,就是大事務(wù)問(wèn)題。

        通常情況下,我們會(huì)在方法上@Transactional注解,填加事務(wù)功能,比如:

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

        @Transactional注解,如果被加到方法上,有個(gè)缺點(diǎn)就是整個(gè)方法都包含在事務(wù)當(dāng)中了。

        上面的這個(gè)例子中,在UserService類中,其實(shí)只有這兩行才需要事務(wù):

        roleService.save(userModel);
        update(userModel);

        在RoleService類中,只有這一行需要事務(wù):

        saveData(userModel);

        現(xiàn)在的這種寫(xiě)法,會(huì)導(dǎo)致所有的query方法也被包含在同一個(gè)事務(wù)當(dāng)中。

        如果query方法非常多,調(diào)用層級(jí)很深,而且有部分查詢方法比較耗時(shí)的話,會(huì)造成整個(gè)事務(wù)非常耗時(shí),而從造成大事務(wù)問(wèn)題。

        關(guān)于大事務(wù)問(wèn)題的危害,可以閱讀一下我的另一篇文章《讓人頭痛的大事務(wù)問(wèn)題到底要如何解決?》,上面有詳細(xì)的講解。

        2.編程式事務(wù)

        上面聊的這些內(nèi)容都是基于@Transactional注解的,主要說(shuō)的是它的事務(wù)問(wèn)題,我們把這種事務(wù)叫做:聲明式事務(wù)。

        其實(shí),spring還提供了另外一種創(chuàng)建事務(wù)的方式,即通過(guò)手動(dòng)編寫(xiě)代碼實(shí)現(xiàn)的事務(wù),我們把這種事務(wù)叫做編程式事務(wù)。例如:


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

        在spring中為了支持編程式事務(wù),專門提供了一個(gè)類:TransactionTemplate,在它的execute方法中,就實(shí)現(xiàn)了事務(wù)的功能。

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

        1. 避免由于spring aop問(wèn)題,導(dǎo)致事務(wù)失效的問(wèn)題。
        2. 能夠更小粒度的控制事務(wù)的范圍,更直觀。

        建議在項(xiàng)目中少使用@Transactional注解開(kāi)啟事務(wù)。但并不是說(shuō)一定不能用它,如果項(xiàng)目中有些業(yè)務(wù)邏輯比較簡(jiǎn)單,而且不經(jīng)常變動(dòng),使用@Transactional注解開(kāi)啟事務(wù)開(kāi)啟事務(wù)也無(wú)妨,因?yàn)樗?jiǎn)單,開(kāi)發(fā)效率更高,但是千萬(wàn)要小心事務(wù)失效的問(wèn)題。

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 激情开心五月天 | 2018henhenr | 艹逼国产 | 操逼黄色网址 | 国产精品亚洲五月天丁香 |