Spring聲明式事務(wù)操作簡單,我們平常開發(fā)過程中,只需要在需要事務(wù)控制的方法上面加上@Transactional注解就可以綁定事務(wù)控制济赎。但是其中的參數(shù)配置今天給大家捋一捋,并且有個(gè)AOP的神坑需要大家注意约巷。
傳播屬性 | 特點(diǎn) |
---|---|
REQUIRED | 默認(rèn)的傳播屬性偎痛,表示如果當(dāng)前環(huán)境存在事務(wù)就保持此事務(wù)執(zhí)行,否則新開一個(gè)事務(wù)并且在新事務(wù)中執(zhí)行 |
REQUIRES_NEW | 表示不管當(dāng)前環(huán)境是否存在事務(wù)独郎,都新建一個(gè)事務(wù)并在新事務(wù)中執(zhí)行踩麦,將原有事務(wù)進(jìn)行掛起 |
SUPPORTS | 表示如果當(dāng)前環(huán)境存在事務(wù),就在此事務(wù)中執(zhí)行氓癌,否則不以事務(wù)方式執(zhí)行 |
NOT_SUPPORTED | 表示此方法不進(jìn)行事務(wù)控制谓谦,如果當(dāng)前環(huán)境存在事務(wù),則掛起 |
MANDATORY | 表示此方法必須在一個(gè)事務(wù)中進(jìn)行贪婉,如果當(dāng)前環(huán)境沒有事務(wù)則拋出異常 |
NEVER | 表示此方法運(yùn)行不能有事務(wù)控制反粥,一旦有事務(wù)傳播至此就拋出異常 |
NESTED | 表示如果事務(wù)存在,則運(yùn)行在一個(gè)嵌套的事務(wù)中疲迂,如果沒有事務(wù)才顿,則按REQUIRED屬性執(zhí)行 |
常用的屬性一般是REQUIRED和REQUIRES_NEW這兩個(gè)。
下面我們用代碼來驗(yàn)證一下這常見的兩個(gè)屬性:
1.開啟事務(wù)支持 @EnableTransactionManagement
/**
* @author wangzhi
*/
@SpringBootApplication
@EnableTransactionManagement
public class DemoApplication {
public static void main(String[] args) {
new SpringApplication(DemoApplication.class).run(args);
}
}
2.實(shí)體類和數(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ù)代碼
我們先檢驗(yàn)一下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ù)傳播有問題尾组,而是動(dòng)態(tài)代理造成的忙芒。在同一個(gè)類里面互相調(diào)用事務(wù)方法,切面切的是發(fā)起事務(wù)的方法讳侨,而內(nèi)部不管調(diào)用的什么事務(wù)方法都會(huì)默認(rèn)為this當(dāng)前對(duì)象去調(diào)動(dòng)普通方法呵萨,這些事務(wù)注解說白了就是不管用。事務(wù)是根據(jù)動(dòng)態(tài)代理生成的動(dòng)態(tài)對(duì)象去執(zhí)行事務(wù)的控制爷耀,所以在同類方法內(nèi)部調(diào)用其他事務(wù)方法必須要獲取其對(duì)應(yīng)的代理對(duì)象去調(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é)果就對(duì)了歹叮!所以我們在開發(fā)過程中遇到A方法調(diào)用B方法跑杭,如果AB方法都在同一個(gè)類里面,想要B方法的事務(wù)生效必須用代理方式執(zhí)行咆耿。當(dāng)然AB方法不在同一個(gè)類里面可以生效德谅,因?yàn)閯?dòng)態(tài)代理是運(yùn)行時(shí)才構(gòu)造的代理對(duì)象。而且萨螺,類似的技術(shù)點(diǎn)不僅僅出現(xiàn)在事務(wù)這里窄做,比如@Async注解同樣還是這樣,同一個(gè)類里面調(diào)用依然不是異步執(zhí)行慰技,當(dāng)涉及到動(dòng)態(tài)代理相關(guān)都要注意此點(diǎn)椭盏。