Spring事務(wù)傳播屬性@Transactional和AOP的坑

Spring聲明式事務(wù)操作簡單,我們平常開發(fā)過程中,只需要在需要事務(wù)控制的方法上面加上@Transactional注解就可以綁定事務(wù)控制。但是其中的參數(shù)配置今天給大家捋一捋,并且有個AOP的神坑需要大家注意。

傳播屬性 特點
REQUIRED 默認的傳播屬性,表示如果當(dāng)前環(huán)境存在事務(wù)就保持此事務(wù)執(zhí)行,否則新開一個事務(wù)并且在新事務(wù)中執(zhí)行
REQUIRES_NEW 表示不管當(dāng)前環(huán)境是否存在事務(wù),都新建一個事務(wù)并在新事務(wù)中執(zhí)行,將原有事務(wù)進行掛起
SUPPORTS 表示如果當(dāng)前環(huán)境存在事務(wù),就在此事務(wù)中執(zhí)行,否則不以事務(wù)方式執(zhí)行
NOT_SUPPORTED 表示此方法不進行事務(wù)控制,如果當(dāng)前環(huán)境存在事務(wù),則掛起
MANDATORY 表示此方法必須在一個事務(wù)中進行,如果當(dāng)前環(huán)境沒有事務(wù)則拋出異常
NEVER 表示此方法運行不能有事務(wù)控制,一旦有事務(wù)傳播至此就拋出異常
NESTED 表示如果事務(wù)存在,則運行在一個嵌套的事務(wù)中,如果沒有事務(wù),則按REQUIRED屬性執(zhí)行

常用的屬性一般是REQUIRED和REQUIRES_NEW這兩個。
下面我們用代碼來驗證一下這常見的兩個屬性:

1.開啟事務(wù)支持 @EnableTransactionManagement

/**
 * @author wangzhi
 */
@SpringBootApplication
@EnableTransactionManagement
public class DemoApplication {
    public static void main(String[] args) {
        new SpringApplication(DemoApplication.class).run(args);
    }
}

2.實體類和數(shù)據(jù)庫

/**
 * @author wangzhi
 */
@Data
@TableName("course")
public class CourseEntity {
    
    @TableId(type = IdType.AUTO)
    private Integer id;
    
    private String courseName;
    
    private BigDecimal price;
}
在這里插入圖片描述

3.service層業(yè)務(wù)代碼

我們先檢驗一下REQUIRED

/**
 * @author wangzhi
 */
@Service
public class TransactionService {

    @Autowired
    private CourseMapper courseMapper;

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void save() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("語文");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //故意制造異常
        System.out.println(1/0);
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void saveInit() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("數(shù)學(xué)");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //調(diào)用save方法
        try{
            save();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4.測試一下,你們猜數(shù)據(jù)庫有幾條記錄?

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = DemoApplication.class)
class DemoApplicationTests {

    @Autowired
    TransactionService transactionService;

    @Test
    public void transactionTest() throws Exception {
        transactionService.saveInit();
    }

}

5.看結(jié)果

在這里插入圖片描述

4.別急,再看看REQUIRES_NEW

/**
 * @author wangzhi
 */
@Service
public class TransactionService {

    @Autowired
    private CourseMapper courseMapper;

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void save() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("語文");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //故意制造異常
        System.out.println(1/0);
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void saveInit() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("數(shù)學(xué)");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //調(diào)用save方法
        try {
            save();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
在這里插入圖片描述

5.之所以出現(xiàn)如此情況,并不是事務(wù)傳播有問題,而是動態(tài)代理造成的。在同一個類里面互相調(diào)用事務(wù)方法,切面切的是發(fā)起事務(wù)的方法,而內(nèi)部不管調(diào)用的什么事務(wù)方法都會默認為this當(dāng)前對象去調(diào)動普通方法,這些事務(wù)注解說白了就是不管用。事務(wù)是根據(jù)動態(tài)代理生成的動態(tài)對象去執(zhí)行事務(wù)的控制,所以在同類方法內(nèi)部調(diào)用其他事務(wù)方法必須要獲取其對應(yīng)的代理對象去調(diào)用才生效,否則必須放在不同的類里面。結(jié)局方法:

6.引入AOP

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

7.@EnableAspectJAutoProxy(exposeProxy = true)

/**
 * @author wangzhi
 */
@SpringBootApplication
@EnableTransactionManagement
@MapperScan("com.example.mapper")
@EnableAspectJAutoProxy(exposeProxy = true)
public class DemoApplication {
    public static void main(String[] args) {
        new SpringApplication(DemoApplication.class).run(args);
    }
}

8.改造一下service

/**
 * @author wangzhi
 */
@Service
public class TransactionService {

    @Autowired
    private CourseMapper courseMapper;

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void save() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("語文");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //故意制造異常
        System.out.println(1/0);
    }

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void saveInit() {
        CourseEntity course = new CourseEntity();
        course.setCourseName("數(shù)學(xué)");
        course.setPrice(new BigDecimal(100));
        courseMapper.insert(course);
        //調(diào)用save方法
        try {
            TransactionService proxy = (TransactionService)AopContext.currentProxy();
            proxy.save();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

看看結(jié)果:


在這里插入圖片描述

這樣的結(jié)果就對了!所以我們在開發(fā)過程中遇到A方法調(diào)用B方法,如果AB方法都在同一個類里面,想要B方法的事務(wù)生效必須用代理方式執(zhí)行。當(dāng)然AB方法不在同一個類里面可以生效,因為動態(tài)代理是運行時才構(gòu)造的代理對象。而且,類似的技術(shù)點不僅僅出現(xiàn)在事務(wù)這里,比如@Async注解同樣還是這樣,同一個類里面調(diào)用依然不是異步執(zhí)行,當(dāng)涉及到動態(tài)代理相關(guān)都要注意此點。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容