Spring事務(wù)的一道面試題
每次聊起Spring事務(wù),好像很熟悉,又好像很陌生。本篇通過(guò)一道面試題和一些實(shí)踐,來(lái)拆解幾個(gè)Spring事務(wù)的常見(jiàn)坑點(diǎn)。
原理
Spring事務(wù)的原理是:通過(guò)AOP切面的方式實(shí)現(xiàn)的,也就是通過(guò)代理模式去實(shí)現(xiàn)事務(wù)增強(qiáng)。
具體過(guò)程是:對(duì)包含@Transactional注解的方法進(jìn)行攔截,然后重寫(xiě),重新在方法里加入異?;貪L的邏輯。而且,每個(gè)線程都是獨(dú)立管理自己的事務(wù),相互隔離。
原理簡(jiǎn)單,使用起來(lái)也簡(jiǎn)單,也就是在方法上打上@Transactional注解,然后事務(wù)就正常生效了。也很少有人去驗(yàn)證異常情況下是否能真正的回滾。
Spring事務(wù)讓我熟悉的地方是哪哪看起來(lái)都簡(jiǎn)單,讓我陌生的地方使用時(shí)的變種較多,有時(shí)候莫名其妙的不生效。
1.源碼
以上原理的相關(guān)源碼如下:
2.實(shí)踐出真知
但是 [半支煙] 偶爾會(huì)在編碼過(guò)程中發(fā)現(xiàn)有些場(chǎng)景下的事務(wù)是失效的,總有些情況讓你想不到,總有一些坑點(diǎn)等你去跳。
[半支煙] 覺(jué)得驗(yàn)證事務(wù)的最好方式就是:記住基本原則 + 動(dòng)手實(shí)踐。記住基本原則可以快速處理常規(guī)問(wèn)題,動(dòng)手實(shí)踐可以驗(yàn)證偏門(mén)問(wèn)題或者不確定的問(wèn)題。
幾種事務(wù)不生效的用法
如下是常見(jiàn)的幾種Spring事務(wù)不生效的用法,有空的讀者一定要牢記,對(duì)日常編碼很有幫助,同時(shí)面試時(shí)也能說(shuō)幾句。
1.private方法
Spring是通過(guò)AOP代理的方式實(shí)現(xiàn)事務(wù)增強(qiáng)的,但是private方法無(wú)法被代理,所以在private方法上打@Transactional注解是不生效的。
2.final、static修飾的方法
和private方法類(lèi)似,final和static修飾的方法也無(wú)法被代理,所以@Transactional注解也不生效。
因?yàn)?,static是屬于類(lèi)方法,final修飾的方法無(wú)法被重寫(xiě),自然也就無(wú)法植入事務(wù)增強(qiáng)代碼。
3.Bean對(duì)象沒(méi)有被Spring托管
某個(gè)類(lèi)一定要被Spring托管,那才能通過(guò)@Transactional注解去增強(qiáng)事務(wù)。如果只有@Transactional注解,而沒(méi)有把類(lèi)交給Spring托管,事務(wù)也是不生效的。類(lèi)似如下情況:
// 此處沒(méi)有@Service注解,此類(lèi)不被spring托管,及時(shí)有@Transactional也不生效
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public final void createAndUpdateUser() {
createUser();
updateUserById();
}
public void createUser() {
User user = new User();
user.setId(2L);
user.setName("test2");
user.setEmail("test2" + "@test.com");
userMapper.insert(user);
System.out.println("create user");
}
public void updateUserById() {
User user = userMapper.findById(1L);
user.setName("admin1");
userMapper.update(user);
int i = 1 / 0; // 此處會(huì)拋出異常
System.out.println("update user");
}
}
4.異常被吞掉
如果在業(yè)務(wù)代碼里,通過(guò)try......catch捕獲了異常,同時(shí)又沒(méi)有繼續(xù)拋出異常時(shí),Spring事務(wù)也是不生效的。
因?yàn)榇碓鰪?qiáng)的邏輯就是要發(fā)現(xiàn)了異常,才能回滾事務(wù)。如果異常被方法本身吞掉了,則代理會(huì)認(rèn)為沒(méi)有異常,從而無(wú)法回滾。
5.非RuntimeException異常
Spring事務(wù)默認(rèn)會(huì)回滾RuntimeException 及其子類(lèi),以及 Error 類(lèi)型的異常。如果是其余異常,則不會(huì)回滾。源碼處可見(jiàn):
這種非RuntimeException異常場(chǎng)景下,需要做2個(gè)動(dòng)作從而保證事務(wù)回滾。
- 捕獲異常,然后拋出自定義異常。
- 自行在@Transactional注解中增加@Transactional(rollbackFor = XxxxxxxException.class)屬性。或者直接使用rollbackFor = Exception.class,也就免去了第一步。
6.異步線程的場(chǎng)景
多個(gè)線程的場(chǎng)景下,只需要牢記每個(gè)線程只管理自己的事務(wù)即可。每個(gè)線程都有一個(gè)獨(dú)立的事務(wù)上下文,存在ThreadLocal中,所以事務(wù)信息在不同線程之間是隔離的。
7.重災(zāi)區(qū):在同一個(gè)類(lèi)中調(diào)用本類(lèi)的方法
這個(gè)失效場(chǎng)景,是最容易出錯(cuò)的,而且變種還多。在同一個(gè)類(lèi)中調(diào)用本類(lèi)的方法時(shí),牢記以下2點(diǎn),即可破局:
- 是否會(huì)開(kāi)啟事務(wù)依賴(lài)此類(lèi)的第一個(gè)被外部調(diào)用的方法。如果此類(lèi)的第一個(gè)被外部調(diào)用的方法有@Transactional注解,那事務(wù)生效。
- 調(diào)用自己內(nèi)部方法時(shí),采用的是this.xxxMethod()的方式,這種方式是不會(huì)走AOP代理的,所以被調(diào)用的內(nèi)部方法的@Transactional注解不生效。
如果確實(shí)需要調(diào)用內(nèi)部方法,并且要事務(wù)生效的話,那只能將被調(diào)用的內(nèi)部方法獨(dú)立到新的類(lèi)中,同時(shí)交給Spring管理。
一道面試題
以上關(guān)于事務(wù)不生效的用法都比較好記,只有在同一個(gè)類(lèi)中調(diào)用本類(lèi)的方法場(chǎng)景下存在多種變種。具體請(qǐng)看這道面試題。請(qǐng)問(wèn)以下createAndUpdateUser方法的事務(wù)生效嗎?
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public final void createAndUpdateUser() { //注意這里有final修飾
createUser();
updateUserById();
}
@Transactional
public void createUser() {
User user = new User();
user.setId(2L);
user.setName("test2");
user.setEmail("test2" + "@test.com");
userMapper.insert(user);
System.out.println("create user");
}
@Transactional(rollbackFor = Exception.class)
public void updateUserById() {
User user = userMapper.findById(1L);
user.setName("admin1");
userMapper.update(user);
int i = 1 / 0; // 此處會(huì)拋出異常
System.out.println("update user");
}
}
如果按照重災(zāi)區(qū):在同一個(gè)類(lèi)中調(diào)用本類(lèi)的方法里提到的2個(gè)原則,則事務(wù)全部生效。
如果按照f(shuō)inal、static修飾的方法里提到的原則,則事務(wù)全部不生效。
那結(jié)果如何呢?結(jié)果是以上方法的事務(wù)全部生效。
為什么呢?這里在補(bǔ)充一個(gè)原則:final修飾的方法如果帶上@Transactional注解,事務(wù)情況按照被調(diào)用的方法自身的事務(wù)托管情況而定。
因?yàn)橐陨洗a中的createUser方法和updateUserById方法,都有@Transactional注解,所以都生效。
這種特殊情況也實(shí)在是讓人瞠目,不過(guò)只需要牢記以上幾種不生效的用法即可,誰(shuí)沒(méi)事兒寫(xiě)這種@Transactional + final的代碼呢?除了面試會(huì)問(wèn)......
總結(jié)
本篇主要聊了幾種事務(wù)不生效的用戶(hù),有興趣的讀者可以記一下。同時(shí),還出了一道特殊場(chǎng)景的面試題,供讀者自行實(shí)踐。希望對(duì)你有幫助!