序言:此前,我們主要通過XML配置Spring來托管事務(wù)。在SpringBoot則非常簡(jiǎn)單,只需在業(yè)務(wù)層添加事務(wù)注解(@Transactional )即可快速開啟事務(wù)。雖然事務(wù)很簡(jiǎn)單,但對(duì)于數(shù)據(jù)方面是需要謹(jǐn)慎對(duì)待的,識(shí)別常見坑點(diǎn)對(duì)我們開發(fā)有幫助。
1. 引入依賴
<!--依賴管理 -->
<dependencies>
<dependency> <!--添加Web依賴 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <!--添加Mybatis依賴 -->
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency><!--添加MySQL驅(qū)動(dòng)依賴 -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency><!--添加Test依賴 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 添加配置
主要是配置數(shù)據(jù)源和開啟Mybatis的自動(dòng)駝峰映射
@SpringBootApplication
public class MybatisTransactionApplication {
public static void main(String[] args) {
//1.初始化
SpringApplication application= new SpringApplication(MybatisTransactionApplication.class);
//2.添加數(shù)據(jù)源
Map<String,Object> map = new HashMap<>();
map.put("spring.datasource.url","jdbc:mysql://localhost:3306/socks?useSSL=false");
map.put("spring.datasource.username","root");
map.put("spring.datasource.password","root");
//3.開啟駝峰映射 (Such as account_id ==> accountId)
map.put("mybatis.configuration.map-underscore-to-camel-case",true);
application.setDefaultProperties(map);
//4.啟動(dòng)應(yīng)用
application.run(args);
}
}
3. 添加數(shù)據(jù)庫記錄
打開 Navicat 的查詢窗口,然后執(zhí)行以下SQL:
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`account_id` varchar(30) ,
`account_name` varchar(30),
`balance` decimal(20,2),
PRIMARY KEY (`account_id`)
);
insert into account values ('1','admin','1000.25');
執(zhí)行完畢后,可以查詢到賬戶數(shù)據(jù),如圖:
4. 編寫代碼
以操作賬戶金額為例,模擬正常操作金額提交事務(wù),以及發(fā)生異常回滾事務(wù)。
其中控制層代碼如下:
package com.hehe.controller;
@RestController
public class AccountController {
@SuppressWarnings("all")
@Autowired
AccountService accountService;
@GetMapping("/")
public Account getAccount() {
//查詢賬戶
return accountService.getAccount();
}
@GetMapping("/add")
public Object addMoney() {
try {
accountService.addMoney();
} catch (Exception e) {
return "發(fā)生異常了:" + accountService.getAccount();
}
return getAccount();
}
}
在業(yè)務(wù)層使用 @Transactional 開啟事務(wù),執(zhí)行數(shù)據(jù)庫操作后拋出異常。具體代碼如下:
package com.hehe.service;
@Service
public class AccountService {
@SuppressWarnings("all")
@Autowired
AccountMapper accountMapper;
public Account getAccount() {
return accountMapper.getAccount();
}
@Transactional
public void addMoney() throws Exception {
//先增加余額
accountMapper.addMoney();
//然后遇到故障
throw new RuntimeException("發(fā)生異常了..");
}
}
數(shù)據(jù)庫層就很簡(jiǎn)單了,我們通過注解來實(shí)現(xiàn)賬戶數(shù)據(jù)的查詢,具體如下:
package com.hehe.mapper;
@Mapper
public interface AccountMapper {
@Select("select * from account where account_id=1")
Account getAccount();
@Update("update account set balance = balance+100 where account_id=1")
void addMoney();
}
其中 Account 實(shí)體對(duì)象如下:
package com.hehe.pojo;
public class Account {
private String accountId;
private String accountName;
private BigDecimal balance;
// Override toString Method ..
// Getter & Setters ..
}
5. 測(cè)試事務(wù)
啟動(dòng)應(yīng)用,訪問 http://localhost:8080 ,可以看到賬戶數(shù)據(jù),如下:
然后訪問 http://localhost:8080/add ,可以看到賬戶余額并沒有增加,如下: 也就是說事務(wù)開啟成功,數(shù)據(jù)得到回滾。
6. 常見坑點(diǎn)
使用事務(wù)注解@Transactional 之前,應(yīng)該先了解它的相關(guān)屬性,避免在實(shí)際項(xiàng)目中踩中各種各樣的坑點(diǎn)。
常見坑點(diǎn)1:遇到檢測(cè)異常時(shí),事務(wù)默認(rèn)不回滾。
例如下面這段代碼,賬戶余額依舊增加成功,并沒有因?yàn)楹竺嬗龅絊QLException(檢測(cè)異常)而進(jìn)行事務(wù)回滾!!
@Transactional
public void addMoney() throws Exception {
//先增加余額
accountMapper.addMoney();
//然后遇到故障
throw new SQLException("發(fā)生異常了..");
}
原因分析:因?yàn)镾pring的默認(rèn)的事務(wù)規(guī)則是遇到運(yùn)行異常(RuntimeException及其子類)和程序錯(cuò)誤(Error)才會(huì)進(jìn)行事務(wù)回滾,顯然SQLException并不屬于這個(gè)范圍。如果想針對(duì)檢測(cè)異常進(jìn)行事務(wù)回滾,可以在@Transactional 注解里使用
rollbackFor 屬性明確指定異常。例如下面這樣,就可以正常回滾:
@Transactional(rollbackFor = Exception.class)
public void addMoney() throws Exception {
//先增加余額
accountMapper.addMoney();
//然后遇到故障
throw new SQLException("發(fā)生異常了..");
}
常見坑點(diǎn)2: 在業(yè)務(wù)層捕捉異常后,發(fā)現(xiàn)事務(wù)不生效。
這是許多新手都會(huì)犯的一個(gè)錯(cuò)誤,在業(yè)務(wù)層手工捕捉并處理了異常,你都把異常“吃”掉了,Spring自然不知道這里有錯(cuò),更不會(huì)主動(dòng)去回滾數(shù)據(jù)。例如:下面這段代碼直接導(dǎo)致增加余額的事務(wù)回滾沒有生效。
@Transactional
public void addMoney() throws Exception {
//先增加余額
accountMapper.addMoney();
//謹(jǐn)慎:盡量不要在業(yè)務(wù)層捕捉異常并處理
try {
throw new SQLException("發(fā)生異常了..");
} catch (Exception e) {
e.printStackTrace();
}
}
不要小瞧了這些細(xì)節(jié),往前暴露異常很大程度上很能夠幫我們快速定位問題,而不是經(jīng)常在項(xiàng)目上線后出現(xiàn)問題,卻無法刨根知道哪里報(bào)錯(cuò)。
推薦做法:若非實(shí)際業(yè)務(wù)要求,則在業(yè)務(wù)層統(tǒng)一拋出異常,然后在控制層統(tǒng)一處理。
@Transactional
public void addMoney() throws Exception {
//先增加余額
accountMapper.addMoney();
//推薦:在業(yè)務(wù)層將異常拋出
throw new RuntimeException("發(fā)生異常了..");
}